Compare commits

..

15 Commits

Author SHA1 Message Date
M2ke4U
0c9fa9d9e1 Merge pull request #117 from MC-XiaoHei/patch-1
fix dumplicate in build.gradle.kts.patch
2025-06-20 22:43:25 +08:00
MC_XiaoHei
a4124d5cda fix dumplicate in build.gradle.kts.patch 2025-06-20 09:37:36 +08:00
Helvetica Volubi
3eedf86153 fix : temporarily fix teleport yam and pitch(#116)
need folia to refix
2025-06-18 23:48:01 +08:00
Helvetica Volubi
68e8267c24 refactor: use standard file structure 2025-06-18 11:56:26 +08:00
HaHaWTH
cd8a4e11dc Guard against malformed input 2025-06-17 18:05:09 +14:00
HaHaWTH
c69087ba64 TickRegion cpu affinity 2025-06-17 16:40:55 +14:00
MrHua269
784ce10986 Do not fire pre creature spawn event unless some plugin is listening it 2025-06-15 17:01:21 +08:00
MrHua269
25b9e3f213 Close channel before replacing file with tmp file 2025-06-15 10:09:03 +08:00
MrHua269
4855a4f76f Updated Upstream(Folia) 2025-06-15 10:06:11 +08:00
MrHua269
818e1132df Updated Upstream(Folia) 2025-06-12 08:03:39 +08:00
MrHua269
4cb423d24a Buffered linear region format 1.0 2025-06-12 07:34:35 +08:00
Helvetica Volubi
6eebbe4221 fix: refix a bug in remove config 2025-06-09 21:47:07 +08:00
Helvetica Volubi
85f3e2875f fix: fix a bug in remove config 2025-06-09 19:18:12 +08:00
MrHua269
07f2fcd405 Port paper commit 9d0aef3b61f6e45ccee02ef9830f8402ada8d340 from upstream's upstream
A temporary fix for memory leaking on AbstractMinecart
2025-06-08 22:29:57 +08:00
Helvetica Volubi
9e3360efa0 fix: fix an error in end platform generate 2025-06-08 21:27:53 +08:00
189 changed files with 6557 additions and 6055 deletions

View File

@@ -2,7 +2,7 @@ group = me.earthme.luminol
version=1.21.5-R0.1-SNAPSHOT
mcVersion=1.21.5
foliaRef=3aba0068ded3f23bf8fa5cac6c70cb48f1d70478
foliaRef=dfa3ca475215e9c496e5aa6629f1897f93f7a7d4
org.gradle.configuration-cache=true
org.gradle.caching=true

View File

@@ -4,177 +4,6 @@ Date: Sun, 12 Jan 2025 13:27:38 +0800
Subject: [PATCH] Pufferfish Sentry
diff --git a/src/main/java/gg/pufferfish/pufferfish/sentry/SentryContext.java b/src/main/java/gg/pufferfish/pufferfish/sentry/SentryContext.java
new file mode 100644
index 0000000000000000000000000000000000000000..c7772aac00f6db664f7a5673bc2585fa025e6aad
--- /dev/null
+++ b/src/main/java/gg/pufferfish/pufferfish/sentry/SentryContext.java
@@ -0,0 +1,165 @@
+package gg.pufferfish.pufferfish.sentry;
+
+import com.google.gson.Gson;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.logging.log4j.ThreadContext;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import org.bukkit.event.Event;
+import org.bukkit.event.player.PlayerEvent;
+import org.bukkit.plugin.Plugin;
+import org.bukkit.plugin.RegisteredListener;
+import org.jetbrains.annotations.Nullable;
+
+public class SentryContext {
+
+ private static final Gson GSON = new Gson();
+
+ public static void setPluginContext(@Nullable Plugin plugin) {
+ if (plugin != null) {
+ ThreadContext.put("pufferfishsentry_pluginname", plugin.getName());
+ ThreadContext.put("pufferfishsentry_pluginversion", plugin.getPluginMeta().getVersion());
+ }
+ }
+
+ public static void removePluginContext() {
+ ThreadContext.remove("pufferfishsentry_pluginname");
+ ThreadContext.remove("pufferfishsentry_pluginversion");
+ }
+
+ public static void setSenderContext(@Nullable CommandSender sender) {
+ if (sender != null) {
+ ThreadContext.put("pufferfishsentry_playername", sender.getName());
+ if (sender instanceof Player player) {
+ ThreadContext.put("pufferfishsentry_playerid", player.getUniqueId().toString());
+ }
+ }
+ }
+
+ public static void removeSenderContext() {
+ ThreadContext.remove("pufferfishsentry_playername");
+ ThreadContext.remove("pufferfishsentry_playerid");
+ }
+
+ public static void setEventContext(Event event, RegisteredListener registration) {
+ setPluginContext(registration.getPlugin());
+
+ try {
+ // Find the player that was involved with this event
+ Player player = null;
+ if (event instanceof PlayerEvent) {
+ player = ((PlayerEvent) event).getPlayer();
+ } else {
+ Class<? extends Event> eventClass = event.getClass();
+
+ Field playerField = null;
+
+ for (Field field : eventClass.getDeclaredFields()) {
+ if (field.getType().equals(Player.class)) {
+ playerField = field;
+ break;
+ }
+ }
+
+ if (playerField != null) {
+ playerField.setAccessible(true);
+ player = (Player) playerField.get(event);
+ }
+ }
+
+ if (player != null) {
+ setSenderContext(player);
+ }
+ } catch (Exception ignored) {
+ } // We can't really safely log exceptions.
+
+ ThreadContext.put("pufferfishsentry_eventdata", GSON.toJson(serializeFields(event)));
+ }
+
+ public static void removeEventContext() {
+ removePluginContext();
+ removeSenderContext();
+ ThreadContext.remove("pufferfishsentry_eventdata");
+ }
+
+ private static Map<String, String> serializeFields(Object object) {
+ Map<String, String> fields = new TreeMap<>();
+ fields.put("_class", object.getClass().getName());
+ for (Field declaredField : object.getClass().getDeclaredFields()) {
+ try {
+ if (Modifier.isStatic(declaredField.getModifiers())) {
+ continue;
+ }
+
+ String fieldName = declaredField.getName();
+ if (fieldName.equals("handlers")) {
+ continue;
+ }
+ declaredField.setAccessible(true);
+ Object value = declaredField.get(object);
+ if (value != null) {
+ fields.put(fieldName, value.toString());
+ } else {
+ fields.put(fieldName, "<null>");
+ }
+ } catch (Exception ignored) {
+ } // We can't really safely log exceptions.
+ }
+ return fields;
+ }
+
+ public static class State {
+
+ private Plugin plugin;
+ private Command command;
+ private String commandLine;
+ private Event event;
+ private RegisteredListener registeredListener;
+
+ public Plugin getPlugin() {
+ return plugin;
+ }
+
+ public void setPlugin(Plugin plugin) {
+ this.plugin = plugin;
+ }
+
+ public Command getCommand() {
+ return command;
+ }
+
+ public void setCommand(Command command) {
+ this.command = command;
+ }
+
+ public String getCommandLine() {
+ return commandLine;
+ }
+
+ public void setCommandLine(String commandLine) {
+ this.commandLine = commandLine;
+ }
+
+ public Event getEvent() {
+ return event;
+ }
+
+ public void setEvent(Event event) {
+ this.event = event;
+ }
+
+ public RegisteredListener getRegisteredListener() {
+ return registeredListener;
+ }
+
+ public void setRegisteredListener(RegisteredListener registeredListener) {
+ this.registeredListener = registeredListener;
+ }
+ }
+}
diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java
index ab36e3aaff57e2f27b5aed06b4bdfe277f86a35e..96da9f1082ab134d197b3a6069f2fcdf38585efe 100644
--- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java

View File

@@ -4,177 +4,6 @@ Date: Sun, 12 Jan 2025 14:00:28 +0800
Subject: [PATCH] Pufferfish SIMD Utilities
diff --git a/src/main/java/gg/pufferfish/pufferfish/simd/SIMDChecker.java b/src/main/java/gg/pufferfish/pufferfish/simd/SIMDChecker.java
new file mode 100644
index 0000000000000000000000000000000000000000..856de1331b15542c00e01990f471fa5152722c11
--- /dev/null
+++ b/src/main/java/gg/pufferfish/pufferfish/simd/SIMDChecker.java
@@ -0,0 +1,35 @@
+package gg.pufferfish.pufferfish.simd;
+
+import jdk.incubator.vector.FloatVector;
+import jdk.incubator.vector.IntVector;
+import jdk.incubator.vector.VectorSpecies;
+import org.slf4j.Logger;
+
+/**
+ * Basically, java is annoying and we have to push this out to its own class.
+ */
+@Deprecated
+public class SIMDChecker {
+
+ @Deprecated
+ public static boolean canEnable(Logger logger) {
+ try {
+ SIMDDetection.testRun = true;
+
+ VectorSpecies<Integer> ISPEC = IntVector.SPECIES_PREFERRED;
+ VectorSpecies<Float> FSPEC = FloatVector.SPECIES_PREFERRED;
+
+ logger.info("Max SIMD vector size on this system is {} bits (int)", ISPEC.vectorBitSize());
+ logger.info("Max SIMD vector size on this system is " + FSPEC.vectorBitSize() + " bits (float)");
+
+ if (ISPEC.elementSize() < 2 || FSPEC.elementSize() < 2) {
+ logger.warn("SIMD is not properly supported on this system!");
+ return false;
+ }
+
+ return true;
+ } catch (NoClassDefFoundError | Exception ignored) {} // Basically, we don't do anything. This lets us detect if it's not functional and disable it.
+ return false;
+ }
+
+}
diff --git a/src/main/java/gg/pufferfish/pufferfish/simd/SIMDDetection.java b/src/main/java/gg/pufferfish/pufferfish/simd/SIMDDetection.java
new file mode 100644
index 0000000000000000000000000000000000000000..0a64cd0e88083ac4af6674ad0fb07b771109c737
--- /dev/null
+++ b/src/main/java/gg/pufferfish/pufferfish/simd/SIMDDetection.java
@@ -0,0 +1,34 @@
+package gg.pufferfish.pufferfish.simd;
+
+import org.slf4j.Logger;
+
+@Deprecated
+public class SIMDDetection {
+
+ public static boolean isEnabled = false;
+ public static boolean testRun = false;
+
+ @Deprecated
+ public static boolean canEnable(Logger logger) {
+ try {
+ return SIMDChecker.canEnable(logger);
+ } catch (NoClassDefFoundError | Exception ignored) {
+ return false;
+ }
+ }
+
+ @Deprecated
+ public static int getJavaVersion() {
+ // https://stackoverflow.com/a/2591122
+ String version = System.getProperty("java.version");
+ if(version.startsWith("1.")) {
+ version = version.substring(2, 3);
+ } else {
+ int dot = version.indexOf(".");
+ if(dot != -1) { version = version.substring(0, dot); }
+ }
+ version = version.split("-")[0]; // Azul is stupid
+ return Integer.parseInt(version);
+ }
+
+}
diff --git a/src/main/java/gg/pufferfish/pufferfish/simd/VectorMapPalette.java b/src/main/java/gg/pufferfish/pufferfish/simd/VectorMapPalette.java
new file mode 100644
index 0000000000000000000000000000000000000000..c26dcaaa2e85882730c854099df80d69eec70f33
--- /dev/null
+++ b/src/main/java/gg/pufferfish/pufferfish/simd/VectorMapPalette.java
@@ -0,0 +1,84 @@
+package gg.pufferfish.pufferfish.simd;
+
+import jdk.incubator.vector.FloatVector;
+import jdk.incubator.vector.IntVector;
+import jdk.incubator.vector.VectorMask;
+import jdk.incubator.vector.VectorSpecies;
+import org.bukkit.map.MapPalette;
+
+import java.awt.*;
+
+@Deprecated
+public class VectorMapPalette {
+
+ private static final VectorSpecies<Integer> I_SPEC = IntVector.SPECIES_PREFERRED;
+ private static final VectorSpecies<Float> F_SPEC = FloatVector.SPECIES_PREFERRED;
+
+ @Deprecated
+ public static void matchColorVectorized(int[] in, byte[] out) {
+ int speciesLength = I_SPEC.length();
+ int i;
+ for (i = 0; i < in.length - speciesLength; i += speciesLength) {
+ float[] redsArr = new float[speciesLength];
+ float[] bluesArr = new float[speciesLength];
+ float[] greensArr = new float[speciesLength];
+ int[] alphasArr = new int[speciesLength];
+
+ for (int j = 0; j < speciesLength; j++) {
+ alphasArr[j] = (in[i + j] >> 24) & 0xFF;
+ redsArr[j] = (in[i + j] >> 16) & 0xFF;
+ greensArr[j] = (in[i + j] >> 8) & 0xFF;
+ bluesArr[j] = (in[i + j] >> 0) & 0xFF;
+ }
+
+ IntVector alphas = IntVector.fromArray(I_SPEC, alphasArr, 0);
+ FloatVector reds = FloatVector.fromArray(F_SPEC, redsArr, 0);
+ FloatVector greens = FloatVector.fromArray(F_SPEC, greensArr, 0);
+ FloatVector blues = FloatVector.fromArray(F_SPEC, bluesArr, 0);
+ IntVector resultIndex = IntVector.zero(I_SPEC);
+ VectorMask<Integer> modificationMask = VectorMask.fromLong(I_SPEC, 0xffffffff);
+
+ modificationMask = modificationMask.and(alphas.lt(128).not());
+ FloatVector bestDistances = FloatVector.broadcast(F_SPEC, Float.MAX_VALUE);
+
+ for (int c = 4; c < MapPalette.colors.length; c++) {
+ // We're using 32-bit floats here because it's 2x faster and nobody will know the difference.
+ // For correctness, the original algorithm uses 64-bit floats instead. Completely unnecessary.
+ FloatVector compReds = FloatVector.broadcast(F_SPEC, MapPalette.colors[c].getRed());
+ FloatVector compGreens = FloatVector.broadcast(F_SPEC, MapPalette.colors[c].getGreen());
+ FloatVector compBlues = FloatVector.broadcast(F_SPEC, MapPalette.colors[c].getBlue());
+
+ FloatVector rMean = reds.add(compReds).div(2.0f);
+ FloatVector rDiff = reds.sub(compReds);
+ FloatVector gDiff = greens.sub(compGreens);
+ FloatVector bDiff = blues.sub(compBlues);
+
+ FloatVector weightR = rMean.div(256.0f).add(2);
+ FloatVector weightG = FloatVector.broadcast(F_SPEC, 4.0f);
+ FloatVector weightB = FloatVector.broadcast(F_SPEC, 255.0f).sub(rMean).div(256.0f).add(2.0f);
+
+ FloatVector distance = weightR.mul(rDiff).mul(rDiff).add(weightG.mul(gDiff).mul(gDiff)).add(weightB.mul(bDiff).mul(bDiff));
+
+ // Now we compare to the best distance we've found.
+ // This mask contains a "1" if better, and a "0" otherwise.
+ VectorMask<Float> bestDistanceMask = distance.lt(bestDistances);
+ bestDistances = bestDistances.blend(distance, bestDistanceMask); // Update the best distances
+
+ // Update the result array
+ // We also AND with the modification mask because we don't want to interfere if the alpha value isn't large enough.
+ resultIndex = resultIndex.blend(c, bestDistanceMask.cast(I_SPEC).and(modificationMask)); // Update the results
+ }
+
+ for (int j = 0; j < speciesLength; j++) {
+ int index = resultIndex.lane(j);
+ out[i + j] = (byte) (index < 128 ? index : -129 + (index - 127));
+ }
+ }
+
+ // For the final ones, fall back to the regular method
+ for (; i < in.length; i++) {
+ out[i] = MapPalette.matchColor(new Color(in[i], true));
+ }
+ }
+
+}
diff --git a/src/main/java/org/bukkit/map/MapPalette.java b/src/main/java/org/bukkit/map/MapPalette.java
index fc9728342de7605da69813fb44b008c1343124c0..d322e6c47d751b41e4b2f2fc45bb8d7498bff21d 100644
--- a/src/main/java/org/bukkit/map/MapPalette.java

View File

@@ -4,193 +4,6 @@ Date: Mon, 27 Jan 2025 13:01:59 +0800
Subject: [PATCH] Tick regions api
diff --git a/src/main/java/me/earthme/luminol/api/RegionStats.java b/src/main/java/me/earthme/luminol/api/RegionStats.java
new file mode 100644
index 0000000000000000000000000000000000000000..96147cace1550d14c682258dab0397587dcf76a4
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/api/RegionStats.java
@@ -0,0 +1,25 @@
+package me.earthme.luminol.api;
+
+/**
+ * A simple package of folia's tick region state.It linked to the RegionStats of the nms part so that</br>
+ * You could call these methods to get the status of this tick region</br>
+ */
+public interface RegionStats {
+ /**
+ * Get the entity count in this tick region
+ * @return the entity count
+ */
+ int getEntityCount();
+
+ /**
+ * Get the player count in this tick region
+ * @return the player count
+ */
+ int getPlayerCount();
+
+ /**
+ * Get the chunk count in this tick region
+ * @return the chunk count
+ */
+ int getChunkCount();
+}
diff --git a/src/main/java/me/earthme/luminol/api/ThreadedRegion.java b/src/main/java/me/earthme/luminol/api/ThreadedRegion.java
new file mode 100644
index 0000000000000000000000000000000000000000..01dac0602b5f66f80c0adfbb779666fe0325a24f
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/api/ThreadedRegion.java
@@ -0,0 +1,56 @@
+package me.earthme.luminol.api;
+
+import org.bukkit.Location;
+import org.bukkit.World;
+
+import javax.annotation.Nullable;
+
+/**
+ * A mirror of folia's ThreadedRegion</br>
+ * Including some handy methods to get the information of the tick region</br>
+ * Note: You should call these methods inside this tick region's thread context
+ */
+public interface ThreadedRegion {
+ /**
+ * Get the center chunk pos of this tick region</br>
+ * Note:</br>
+ * 1.Global region will return a null value(But we don't finish the global region yet()</br>
+ * 2.You should call these methods inside this tick region's thread context
+ * @return The center chunk pos
+ */
+ @Nullable
+ Location getCenterChunkPos();
+
+ /**
+ * Get the dead section percent of this tick region
+ * Note: </br>
+ * 1.Dead percent is mean the percent of the unloaded chunk count of this tick region, which is also used for determine
+ * that the tick region should or not check for splitting</br>
+ * 2.You should call these methods inside this tick region's thread context
+ * @return The dead section percent
+ */
+ double getDeadSectionPercent();
+
+ /**
+ * Get the tick region data of this tick region</br>
+ * Note:</br>
+ * 1.You should call this method inside this tick region's thread context</br>
+ * 2.You should call these methods inside this tick region's thread context
+ * @return The tick region data
+ */
+ TickRegionData getTickRegionData();
+
+ /**
+ * Get the world of this tick region</br>
+ * Note: Global region will return a null value too
+ * @return The world of this tick region
+ */
+ @Nullable
+ World getWorld();
+
+ /**
+ * Get the id of the tick region</br>
+ * @return The id of the tick region
+ */
+ long getId();
+}
diff --git a/src/main/java/me/earthme/luminol/api/ThreadedRegionizer.java b/src/main/java/me/earthme/luminol/api/ThreadedRegionizer.java
new file mode 100644
index 0000000000000000000000000000000000000000..ff31a68a019fd9e5e687e6818f8729f4950bc060
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/api/ThreadedRegionizer.java
@@ -0,0 +1,56 @@
+package me.earthme.luminol.api;
+
+import org.bukkit.Location;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Collection;
+
+/**
+ * A mirror of folia's ThreadedRegionizer
+ */
+public interface ThreadedRegionizer {
+ /**
+ * Get all the tick regions
+ * @return Temporary copied collection of all tick regions
+ */
+ Collection<ThreadedRegion> getAllRegions();
+
+ /**
+ * Get the tick region at the given chunk coordinates
+ * @param chunkX Chunk X
+ * @param chunkZ Chunk Z
+ * @return The tick region at the given chunk coordinates
+ */
+ @Nullable
+ ThreadedRegion getAtSynchronized(int chunkX, int chunkZ);
+
+ /**
+ * Get the tick region at the given chunk coordinates
+ * @param chunkX Chunk X
+ * @param chunkZ Chunk Z
+ * @return The tick region at the given chunk coordinates
+ */
+ @Nullable
+ ThreadedRegion getAtUnSynchronized(int chunkX, int chunkZ);
+
+ /**
+ * Get the tick region at the given location
+ * @param pos The location
+ * @return The tick region at the given location
+ */
+ @Nullable
+ default ThreadedRegion getAtSynchronized(@NotNull Location pos) {
+ return this.getAtSynchronized(pos.getBlockX() >> 4, pos.getBlockZ() >> 4);
+ }
+
+ /**
+ * Get the tick region at the given location
+ * @param pos The location
+ * @return The tick region at the given location
+ */
+ @Nullable
+ default ThreadedRegion getAtUnSynchronized(@NotNull Location pos) {
+ return this.getAtUnSynchronized(pos.getBlockX() >> 4, pos.getBlockZ() >> 4);
+ }
+}
diff --git a/src/main/java/me/earthme/luminol/api/TickRegionData.java b/src/main/java/me/earthme/luminol/api/TickRegionData.java
new file mode 100644
index 0000000000000000000000000000000000000000..ecde4462b08d701b8bff9f26902f17754cf791dd
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/api/TickRegionData.java
@@ -0,0 +1,26 @@
+package me.earthme.luminol.api;
+
+import org.bukkit.World;
+
+/**
+ * A mirror of folia's tick region data
+ */
+public interface TickRegionData {
+ /**
+ * Get the world it's currently holding
+ * @return the world
+ */
+ World getWorld();
+
+ /**
+ * Get the current tick count
+ * @return the current tick count
+ */
+ long getCurrentTickCount();
+
+ /**
+ * Get the region stats
+ * @return the region stats
+ */
+ RegionStats getRegionStats();
+}
diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java
index a8b64f78bf3c453094074b4b4d3c8fd07b9eb273..7927012c1afe5289d22879353a88a4574da91e01 100644
--- a/src/main/java/org/bukkit/World.java

View File

@@ -1,17 +1,17 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <wangxyper@163.com>
Date: Thu, 30 Jan 2025 09:29:03 +0800
From: MrHua269 <mrhua269@gmail.com>
Date: Thu, 12 Jun 2025 08:00:15 +0800
Subject: [PATCH] Purpur Lobotomize stuck villagers
diff --git a/src/main/java/org/bukkit/entity/Villager.java b/src/main/java/org/bukkit/entity/Villager.java
index 02b86d9615f8150b13ff0beefd5ca502c0494f99..3a444609ea9fdeee9057d593fbd4d38cc9e1ad68 100644
index 4d88bb2eaa43709fb6103a6f77d8c01e83bfe743..60b87d52c20cec947b196f47fc333bc643accbd2 100644
--- a/src/main/java/org/bukkit/entity/Villager.java
+++ b/src/main/java/org/bukkit/entity/Villager.java
@@ -391,4 +391,13 @@ public interface Villager extends AbstractVillager {
* reputation regardless of its impact and the player associated.
@@ -408,4 +408,13 @@ public interface Villager extends AbstractVillager {
* Demand is still updated even if all events are canceled.
*/
public void clearReputations();
public void restock();
+ // Purpur start
+
+ /**

View File

@@ -1,357 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <wangxyper@163.com>
Date: Fri, 31 Jan 2025 20:28:47 +0800
Subject: [PATCH] Add missing teleportation apis for folia
diff --git a/src/main/java/me/earthme/luminol/api/entity/EntityTeleportAsyncEvent.java b/src/main/java/me/earthme/luminol/api/entity/EntityTeleportAsyncEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..a31c803831dad3d31386924cbe27deff59855fc9
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/api/entity/EntityTeleportAsyncEvent.java
@@ -0,0 +1,68 @@
+package me.earthme.luminol.api.entity;
+
+import org.apache.commons.lang3.Validate;
+import org.bukkit.Location;
+import org.bukkit.entity.Entity;
+import org.bukkit.event.Cancellable;
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+import org.bukkit.event.player.PlayerTeleportEvent;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * A simple event fired when a teleportAsync was called
+ * @see org.bukkit.entity.Entity#teleportAsync(org.bukkit.Location, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause)
+ * @see org.bukkit.entity.Entity#teleportAsync(org.bukkit.Location)
+ * (Also fired when teleportAsync called from nms)
+ */
+public class EntityTeleportAsyncEvent extends Event {
+ private static final HandlerList HANDLERS = new HandlerList();
+
+ private final Entity entity;
+ private final PlayerTeleportEvent.TeleportCause teleportCause;
+ private final Location destination;
+
+ public EntityTeleportAsyncEvent(Entity entity, PlayerTeleportEvent.TeleportCause teleportCause, Location destination) {
+ Validate.notNull(entity, "entity cannot be a null value!");
+ Validate.notNull(teleportCause, "teleportCause cannot be a null value!");
+ Validate.notNull(destination, "destination cannot be a null value!");
+
+ this.entity = entity;
+ this.teleportCause = teleportCause;
+ this.destination = destination;
+ }
+
+ /**
+ * Get the entity is about to be teleported
+ * @return that entity
+ */
+ public @NotNull Entity getEntity() {
+ return this.entity;
+ }
+
+ /**
+ * Get the cause of the teleport
+ * @return the cause
+ */
+ public @NotNull PlayerTeleportEvent.TeleportCause getTeleportCause() {
+ return this.teleportCause;
+ }
+
+ /**
+ * Get the destination of the teleport
+ * @return the destination
+ */
+ public @NotNull Location getDestination() {
+ return this.destination;
+ }
+
+ @Override
+ public @NotNull HandlerList getHandlers() {
+ return HANDLERS;
+ }
+
+ @NotNull
+ public static HandlerList getHandlerList() {
+ return HANDLERS;
+ }
+}
diff --git a/src/main/java/me/earthme/luminol/api/entity/PostEntityPortalEvent.java b/src/main/java/me/earthme/luminol/api/entity/PostEntityPortalEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..dd3087b407ccf4e96448701e6fbf75705498f982
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/api/entity/PostEntityPortalEvent.java
@@ -0,0 +1,41 @@
+package me.earthme.luminol.api.entity;
+
+import org.apache.commons.lang3.Validate;
+import org.bukkit.entity.Entity;
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * A simple event created for missing teleport events api of folia
+ * This event is fired when the entity portal process has been done
+ */
+public class PostEntityPortalEvent extends Event {
+ private static final HandlerList HANDLER_LIST = new HandlerList();
+
+ private final Entity teleportedEntity;
+
+ public PostEntityPortalEvent(Entity teleportedEntity) {
+ Validate.notNull(teleportedEntity, "teleportedEntity cannot be null!");
+
+ this.teleportedEntity = teleportedEntity;
+ }
+
+ /**
+ * Get the entity which was teleported
+ * @return the entity which was teleported
+ */
+ public Entity getTeleportedEntity() {
+ return this.teleportedEntity;
+ }
+
+ @Override
+ public @NotNull HandlerList getHandlers() {
+ return HANDLER_LIST;
+ }
+
+ @NotNull
+ public static HandlerList getHandlerList() {
+ return HANDLER_LIST;
+ }
+}
diff --git a/src/main/java/me/earthme/luminol/api/entity/PreEntityPortalEvent.java b/src/main/java/me/earthme/luminol/api/entity/PreEntityPortalEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..fc844429e3ecfe2529c0a49b8a5d958eeb188ad9
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/api/entity/PreEntityPortalEvent.java
@@ -0,0 +1,78 @@
+package me.earthme.luminol.api.entity;
+
+import org.apache.commons.lang3.Validate;
+import org.bukkit.Location;
+import org.bukkit.World;
+import org.bukkit.entity.Entity;
+import org.bukkit.event.Cancellable;
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * A simple event created for missing teleport events api of folia
+ * This event will be fired when a portal teleportation is about to happen
+ */
+public class PreEntityPortalEvent extends Event implements Cancellable {
+ private static final HandlerList HANDLERS = new HandlerList();
+
+ private final Entity entity;
+ private final Location portalPos;
+ private final World destination;
+
+ private boolean cancelled = false;
+
+ public PreEntityPortalEvent(Entity entity, Location portalPos, World destination) {
+ Validate.notNull(entity, "entity cannot be null!");
+ Validate.notNull(portalPos, "portalPos cannot be null!");
+ Validate.notNull(destination, "destination cannot be null!");
+
+ this.entity = entity;
+ this.portalPos = portalPos;
+ this.destination = destination;
+ }
+
+ /**
+ * Get the entity that is about to teleport
+ * @return the entity
+ */
+ public @NotNull Entity getEntity() {
+ return this.entity;
+ }
+
+ /**
+ * Get the location of the portal
+ * @return the portal location
+ */
+ public @NotNull Location getPortalPos() {
+ return this.portalPos;
+ }
+
+ /**
+ * Get the destination world
+ * @return the destination world
+ */
+ public @NotNull World getDestination() {
+ return this.destination;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return this.cancelled;
+ }
+
+ @Override
+ public void setCancelled(boolean cancel) {
+ this.cancelled = cancel;
+ }
+
+ @Override
+ public @NotNull HandlerList getHandlers() {
+ return HANDLERS;
+ }
+
+ @NotNull
+ public static HandlerList getHandlerList() {
+ return HANDLERS;
+ }
+}
diff --git a/src/main/java/me/earthme/luminol/api/entity/player/PostPlayerRespawnEvent.java b/src/main/java/me/earthme/luminol/api/entity/player/PostPlayerRespawnEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..9a561455560dfeee1d8762297ebf15a7c11de4d1
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/api/entity/player/PostPlayerRespawnEvent.java
@@ -0,0 +1,40 @@
+package me.earthme.luminol.api.entity.player;
+
+import org.apache.commons.lang3.Validate;
+import org.bukkit.entity.Player;
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * A simple event fired when the respawn process of player is done
+ */
+public class PostPlayerRespawnEvent extends Event {
+ private static final HandlerList HANDLERS = new HandlerList();
+
+ private final Player player;
+
+ public PostPlayerRespawnEvent(Player player) {
+ Validate.notNull(player, "Player cannot be a null value!");
+
+ this.player = player;
+ }
+
+ /**
+ * Get the respawned player
+ * @return the player
+ */
+ public @NotNull Player getPlayer() {
+ return this.player;
+ }
+
+ @Override
+ public @NotNull HandlerList getHandlers() {
+ return HANDLERS;
+ }
+
+ @NotNull
+ public static HandlerList getHandlerList() {
+ return HANDLERS;
+ }
+}
diff --git a/src/main/java/me/earthme/luminol/api/portal/EndPlatformCreateEvent.java b/src/main/java/me/earthme/luminol/api/portal/EndPlatformCreateEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..cf87a7cce5d1ebec9709b762595609344807150b
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/api/portal/EndPlatformCreateEvent.java
@@ -0,0 +1,35 @@
+package me.earthme.luminol.api.portal;
+
+import org.bukkit.event.Cancellable;
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * A event fired when an end platform is created.
+ */
+public class EndPlatformCreateEvent extends Event implements Cancellable {
+ private static final HandlerList HANDLERS = new HandlerList();
+
+ private boolean cancelled = false;
+
+ @Override
+ public boolean isCancelled() {
+ return this.cancelled;
+ }
+
+ @Override
+ public void setCancelled(boolean cancel) {
+ this.cancelled = cancel;
+ }
+
+ @Override
+ public @NotNull HandlerList getHandlers() {
+ return HANDLERS;
+ }
+
+ @NotNull
+ public static HandlerList getHandlerList() {
+ return HANDLERS;
+ }
+}
diff --git a/src/main/java/me/earthme/luminol/api/portal/PortalLocateEvent.java b/src/main/java/me/earthme/luminol/api/portal/PortalLocateEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..e09ffb99aad6f6acca3d6a411877715b90413eb0
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/api/portal/PortalLocateEvent.java
@@ -0,0 +1,53 @@
+package me.earthme.luminol.api.portal;
+
+import org.apache.commons.lang3.Validate;
+import org.bukkit.Location;
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * A event fired when the portal process started locating the destination position
+ * Notice: If you changed the destination to an another position in end teleportation.The end platform won't create under the entity and won't create
+ * if the position is out of current tick region
+ */
+public class PortalLocateEvent extends Event {
+ private static final HandlerList HANDLERS = new HandlerList();
+
+ private final Location original;
+ private final Location destination;
+
+ public PortalLocateEvent(Location original, Location destination) {
+ Validate.notNull(original, "original couldn't be null!");
+ Validate.notNull(destination, "destination couldn't be null!");
+
+ this.original = original;
+ this.destination = destination;
+ }
+
+ /**
+ * Get the destination position of this teleportation
+ * @return the destination position
+ */
+ public Location getDestination() {
+ return this.destination;
+ }
+
+ /**
+ * Get the original portal position of this teleportation
+ * @return the original portal position
+ */
+ public Location getOriginal() {
+ return this.original;
+ }
+
+ @Override
+ public @NotNull HandlerList getHandlers() {
+ return HANDLERS;
+ }
+
+ @NotNull
+ public static HandlerList getHandlerList() {
+ return HANDLERS;
+ }
+}

View File

@@ -0,0 +1,165 @@
package gg.pufferfish.pufferfish.sentry;
import com.google.gson.Gson;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.TreeMap;
import org.apache.logging.log4j.ThreadContext;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.event.player.PlayerEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredListener;
import org.jetbrains.annotations.Nullable;
public class SentryContext {
private static final Gson GSON = new Gson();
public static void setPluginContext(@Nullable Plugin plugin) {
if (plugin != null) {
ThreadContext.put("pufferfishsentry_pluginname", plugin.getName());
ThreadContext.put("pufferfishsentry_pluginversion", plugin.getPluginMeta().getVersion());
}
}
public static void removePluginContext() {
ThreadContext.remove("pufferfishsentry_pluginname");
ThreadContext.remove("pufferfishsentry_pluginversion");
}
public static void setSenderContext(@Nullable CommandSender sender) {
if (sender != null) {
ThreadContext.put("pufferfishsentry_playername", sender.getName());
if (sender instanceof Player player) {
ThreadContext.put("pufferfishsentry_playerid", player.getUniqueId().toString());
}
}
}
public static void removeSenderContext() {
ThreadContext.remove("pufferfishsentry_playername");
ThreadContext.remove("pufferfishsentry_playerid");
}
public static void setEventContext(Event event, RegisteredListener registration) {
setPluginContext(registration.getPlugin());
try {
// Find the player that was involved with this event
Player player = null;
if (event instanceof PlayerEvent) {
player = ((PlayerEvent) event).getPlayer();
} else {
Class<? extends Event> eventClass = event.getClass();
Field playerField = null;
for (Field field : eventClass.getDeclaredFields()) {
if (field.getType().equals(Player.class)) {
playerField = field;
break;
}
}
if (playerField != null) {
playerField.setAccessible(true);
player = (Player) playerField.get(event);
}
}
if (player != null) {
setSenderContext(player);
}
} catch (Exception ignored) {
} // We can't really safely log exceptions.
ThreadContext.put("pufferfishsentry_eventdata", GSON.toJson(serializeFields(event)));
}
public static void removeEventContext() {
removePluginContext();
removeSenderContext();
ThreadContext.remove("pufferfishsentry_eventdata");
}
private static Map<String, String> serializeFields(Object object) {
Map<String, String> fields = new TreeMap<>();
fields.put("_class", object.getClass().getName());
for (Field declaredField : object.getClass().getDeclaredFields()) {
try {
if (Modifier.isStatic(declaredField.getModifiers())) {
continue;
}
String fieldName = declaredField.getName();
if (fieldName.equals("handlers")) {
continue;
}
declaredField.setAccessible(true);
Object value = declaredField.get(object);
if (value != null) {
fields.put(fieldName, value.toString());
} else {
fields.put(fieldName, "<null>");
}
} catch (Exception ignored) {
} // We can't really safely log exceptions.
}
return fields;
}
public static class State {
private Plugin plugin;
private Command command;
private String commandLine;
private Event event;
private RegisteredListener registeredListener;
public Plugin getPlugin() {
return plugin;
}
public void setPlugin(Plugin plugin) {
this.plugin = plugin;
}
public Command getCommand() {
return command;
}
public void setCommand(Command command) {
this.command = command;
}
public String getCommandLine() {
return commandLine;
}
public void setCommandLine(String commandLine) {
this.commandLine = commandLine;
}
public Event getEvent() {
return event;
}
public void setEvent(Event event) {
this.event = event;
}
public RegisteredListener getRegisteredListener() {
return registeredListener;
}
public void setRegisteredListener(RegisteredListener registeredListener) {
this.registeredListener = registeredListener;
}
}
}

View File

@@ -0,0 +1,35 @@
package gg.pufferfish.pufferfish.simd;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.IntVector;
import jdk.incubator.vector.VectorSpecies;
import org.slf4j.Logger;
/**
* Basically, java is annoying and we have to push this out to its own class.
*/
@Deprecated
public class SIMDChecker {
@Deprecated
public static boolean canEnable(Logger logger) {
try {
SIMDDetection.testRun = true;
VectorSpecies<Integer> ISPEC = IntVector.SPECIES_PREFERRED;
VectorSpecies<Float> FSPEC = FloatVector.SPECIES_PREFERRED;
logger.info("Max SIMD vector size on this system is {} bits (int)", ISPEC.vectorBitSize());
logger.info("Max SIMD vector size on this system is " + FSPEC.vectorBitSize() + " bits (float)");
if (ISPEC.elementSize() < 2 || FSPEC.elementSize() < 2) {
logger.warn("SIMD is not properly supported on this system!");
return false;
}
return true;
} catch (NoClassDefFoundError | Exception ignored) {} // Basically, we don't do anything. This lets us detect if it's not functional and disable it.
return false;
}
}

View File

@@ -0,0 +1,34 @@
package gg.pufferfish.pufferfish.simd;
import org.slf4j.Logger;
@Deprecated
public class SIMDDetection {
public static boolean isEnabled = false;
public static boolean testRun = false;
@Deprecated
public static boolean canEnable(Logger logger) {
try {
return SIMDChecker.canEnable(logger);
} catch (NoClassDefFoundError | Exception ignored) {
return false;
}
}
@Deprecated
public static int getJavaVersion() {
// https://stackoverflow.com/a/2591122
String version = System.getProperty("java.version");
if(version.startsWith("1.")) {
version = version.substring(2, 3);
} else {
int dot = version.indexOf(".");
if(dot != -1) { version = version.substring(0, dot); }
}
version = version.split("-")[0]; // Azul is stupid
return Integer.parseInt(version);
}
}

View File

@@ -0,0 +1,84 @@
package gg.pufferfish.pufferfish.simd;
import jdk.incubator.vector.FloatVector;
import jdk.incubator.vector.IntVector;
import jdk.incubator.vector.VectorMask;
import jdk.incubator.vector.VectorSpecies;
import org.bukkit.map.MapPalette;
import java.awt.*;
@Deprecated
public class VectorMapPalette {
private static final VectorSpecies<Integer> I_SPEC = IntVector.SPECIES_PREFERRED;
private static final VectorSpecies<Float> F_SPEC = FloatVector.SPECIES_PREFERRED;
@Deprecated
public static void matchColorVectorized(int[] in, byte[] out) {
int speciesLength = I_SPEC.length();
int i;
for (i = 0; i < in.length - speciesLength; i += speciesLength) {
float[] redsArr = new float[speciesLength];
float[] bluesArr = new float[speciesLength];
float[] greensArr = new float[speciesLength];
int[] alphasArr = new int[speciesLength];
for (int j = 0; j < speciesLength; j++) {
alphasArr[j] = (in[i + j] >> 24) & 0xFF;
redsArr[j] = (in[i + j] >> 16) & 0xFF;
greensArr[j] = (in[i + j] >> 8) & 0xFF;
bluesArr[j] = (in[i + j] >> 0) & 0xFF;
}
IntVector alphas = IntVector.fromArray(I_SPEC, alphasArr, 0);
FloatVector reds = FloatVector.fromArray(F_SPEC, redsArr, 0);
FloatVector greens = FloatVector.fromArray(F_SPEC, greensArr, 0);
FloatVector blues = FloatVector.fromArray(F_SPEC, bluesArr, 0);
IntVector resultIndex = IntVector.zero(I_SPEC);
VectorMask<Integer> modificationMask = VectorMask.fromLong(I_SPEC, 0xffffffff);
modificationMask = modificationMask.and(alphas.lt(128).not());
FloatVector bestDistances = FloatVector.broadcast(F_SPEC, Float.MAX_VALUE);
for (int c = 4; c < MapPalette.colors.length; c++) {
// We're using 32-bit floats here because it's 2x faster and nobody will know the difference.
// For correctness, the original algorithm uses 64-bit floats instead. Completely unnecessary.
FloatVector compReds = FloatVector.broadcast(F_SPEC, MapPalette.colors[c].getRed());
FloatVector compGreens = FloatVector.broadcast(F_SPEC, MapPalette.colors[c].getGreen());
FloatVector compBlues = FloatVector.broadcast(F_SPEC, MapPalette.colors[c].getBlue());
FloatVector rMean = reds.add(compReds).div(2.0f);
FloatVector rDiff = reds.sub(compReds);
FloatVector gDiff = greens.sub(compGreens);
FloatVector bDiff = blues.sub(compBlues);
FloatVector weightR = rMean.div(256.0f).add(2);
FloatVector weightG = FloatVector.broadcast(F_SPEC, 4.0f);
FloatVector weightB = FloatVector.broadcast(F_SPEC, 255.0f).sub(rMean).div(256.0f).add(2.0f);
FloatVector distance = weightR.mul(rDiff).mul(rDiff).add(weightG.mul(gDiff).mul(gDiff)).add(weightB.mul(bDiff).mul(bDiff));
// Now we compare to the best distance we've found.
// This mask contains a "1" if better, and a "0" otherwise.
VectorMask<Float> bestDistanceMask = distance.lt(bestDistances);
bestDistances = bestDistances.blend(distance, bestDistanceMask); // Update the best distances
// Update the result array
// We also AND with the modification mask because we don't want to interfere if the alpha value isn't large enough.
resultIndex = resultIndex.blend(c, bestDistanceMask.cast(I_SPEC).and(modificationMask)); // Update the results
}
for (int j = 0; j < speciesLength; j++) {
int index = resultIndex.lane(j);
out[i + j] = (byte) (index < 128 ? index : -129 + (index - 127));
}
}
// For the final ones, fall back to the regular method
for (; i < in.length; i++) {
out[i] = MapPalette.matchColor(new Color(in[i], true));
}
}
}

View File

@@ -0,0 +1,25 @@
package me.earthme.luminol.api;
/**
* A simple package of folia's tick region state.It linked to the RegionStats of the nms part so that</br>
* You could call these methods to get the status of this tick region</br>
*/
public interface RegionStats {
/**
* Get the entity count in this tick region
* @return the entity count
*/
int getEntityCount();
/**
* Get the player count in this tick region
* @return the player count
*/
int getPlayerCount();
/**
* Get the chunk count in this tick region
* @return the chunk count
*/
int getChunkCount();
}

View File

@@ -0,0 +1,56 @@
package me.earthme.luminol.api;
import org.bukkit.Location;
import org.bukkit.World;
import javax.annotation.Nullable;
/**
* A mirror of folia's ThreadedRegion</br>
* Including some handy methods to get the information of the tick region</br>
* Note: You should call these methods inside this tick region's thread context
*/
public interface ThreadedRegion {
/**
* Get the center chunk pos of this tick region</br>
* Note:</br>
* 1.Global region will return a null value(But we don't finish the global region yet()</br>
* 2.You should call these methods inside this tick region's thread context
* @return The center chunk pos
*/
@Nullable
Location getCenterChunkPos();
/**
* Get the dead section percent of this tick region
* Note: </br>
* 1.Dead percent is mean the percent of the unloaded chunk count of this tick region, which is also used for determine
* that the tick region should or not check for splitting</br>
* 2.You should call these methods inside this tick region's thread context
* @return The dead section percent
*/
double getDeadSectionPercent();
/**
* Get the tick region data of this tick region</br>
* Note:</br>
* 1.You should call this method inside this tick region's thread context</br>
* 2.You should call these methods inside this tick region's thread context
* @return The tick region data
*/
TickRegionData getTickRegionData();
/**
* Get the world of this tick region</br>
* Note: Global region will return a null value too
* @return The world of this tick region
*/
@Nullable
World getWorld();
/**
* Get the id of the tick region</br>
* @return The id of the tick region
*/
long getId();
}

View File

@@ -0,0 +1,56 @@
package me.earthme.luminol.api;
import org.bukkit.Location;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
/**
* A mirror of folia's ThreadedRegionizer
*/
public interface ThreadedRegionizer {
/**
* Get all the tick regions
* @return Temporary copied collection of all tick regions
*/
Collection<ThreadedRegion> getAllRegions();
/**
* Get the tick region at the given chunk coordinates
* @param chunkX Chunk X
* @param chunkZ Chunk Z
* @return The tick region at the given chunk coordinates
*/
@Nullable
ThreadedRegion getAtSynchronized(int chunkX, int chunkZ);
/**
* Get the tick region at the given chunk coordinates
* @param chunkX Chunk X
* @param chunkZ Chunk Z
* @return The tick region at the given chunk coordinates
*/
@Nullable
ThreadedRegion getAtUnSynchronized(int chunkX, int chunkZ);
/**
* Get the tick region at the given location
* @param pos The location
* @return The tick region at the given location
*/
@Nullable
default ThreadedRegion getAtSynchronized(@NotNull Location pos) {
return this.getAtSynchronized(pos.getBlockX() >> 4, pos.getBlockZ() >> 4);
}
/**
* Get the tick region at the given location
* @param pos The location
* @return The tick region at the given location
*/
@Nullable
default ThreadedRegion getAtUnSynchronized(@NotNull Location pos) {
return this.getAtUnSynchronized(pos.getBlockX() >> 4, pos.getBlockZ() >> 4);
}
}

View File

@@ -0,0 +1,26 @@
package me.earthme.luminol.api;
import org.bukkit.World;
/**
* A mirror of folia's tick region data
*/
public interface TickRegionData {
/**
* Get the world it's currently holding
* @return the world
*/
World getWorld();
/**
* Get the current tick count
* @return the current tick count
*/
long getCurrentTickCount();
/**
* Get the region stats
* @return the region stats
*/
RegionStats getRegionStats();
}

View File

@@ -0,0 +1,68 @@
package me.earthme.luminol.api.entity;
import org.apache.commons.lang3.Validate;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.jetbrains.annotations.NotNull;
/**
* A simple event fired when a teleportAsync was called
* @see org.bukkit.entity.Entity#teleportAsync(org.bukkit.Location, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause)
* @see org.bukkit.entity.Entity#teleportAsync(org.bukkit.Location)
* (Also fired when teleportAsync called from nms)
*/
public class EntityTeleportAsyncEvent extends Event {
private static final HandlerList HANDLERS = new HandlerList();
private final Entity entity;
private final PlayerTeleportEvent.TeleportCause teleportCause;
private final Location destination;
public EntityTeleportAsyncEvent(Entity entity, PlayerTeleportEvent.TeleportCause teleportCause, Location destination) {
Validate.notNull(entity, "entity cannot be a null value!");
Validate.notNull(teleportCause, "teleportCause cannot be a null value!");
Validate.notNull(destination, "destination cannot be a null value!");
this.entity = entity;
this.teleportCause = teleportCause;
this.destination = destination;
}
/**
* Get the entity is about to be teleported
* @return that entity
*/
public @NotNull Entity getEntity() {
return this.entity;
}
/**
* Get the cause of the teleport
* @return the cause
*/
public @NotNull PlayerTeleportEvent.TeleportCause getTeleportCause() {
return this.teleportCause;
}
/**
* Get the destination of the teleport
* @return the destination
*/
public @NotNull Location getDestination() {
return this.destination;
}
@Override
public @NotNull HandlerList getHandlers() {
return HANDLERS;
}
@NotNull
public static HandlerList getHandlerList() {
return HANDLERS;
}
}

View File

@@ -0,0 +1,41 @@
package me.earthme.luminol.api.entity;
import org.apache.commons.lang3.Validate;
import org.bukkit.entity.Entity;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* A simple event created for missing teleport events api of folia
* This event is fired when the entity portal process has been done
*/
public class PostEntityPortalEvent extends Event {
private static final HandlerList HANDLER_LIST = new HandlerList();
private final Entity teleportedEntity;
public PostEntityPortalEvent(Entity teleportedEntity) {
Validate.notNull(teleportedEntity, "teleportedEntity cannot be null!");
this.teleportedEntity = teleportedEntity;
}
/**
* Get the entity which was teleported
* @return the entity which was teleported
*/
public Entity getTeleportedEntity() {
return this.teleportedEntity;
}
@Override
public @NotNull HandlerList getHandlers() {
return HANDLER_LIST;
}
@NotNull
public static HandlerList getHandlerList() {
return HANDLER_LIST;
}
}

View File

@@ -0,0 +1,78 @@
package me.earthme.luminol.api.entity;
import org.apache.commons.lang3.Validate;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* A simple event created for missing teleport events api of folia
* This event will be fired when a portal teleportation is about to happen
*/
public class PreEntityPortalEvent extends Event implements Cancellable {
private static final HandlerList HANDLERS = new HandlerList();
private final Entity entity;
private final Location portalPos;
private final World destination;
private boolean cancelled = false;
public PreEntityPortalEvent(Entity entity, Location portalPos, World destination) {
Validate.notNull(entity, "entity cannot be null!");
Validate.notNull(portalPos, "portalPos cannot be null!");
Validate.notNull(destination, "destination cannot be null!");
this.entity = entity;
this.portalPos = portalPos;
this.destination = destination;
}
/**
* Get the entity that is about to teleport
* @return the entity
*/
public @NotNull Entity getEntity() {
return this.entity;
}
/**
* Get the location of the portal
* @return the portal location
*/
public @NotNull Location getPortalPos() {
return this.portalPos;
}
/**
* Get the destination world
* @return the destination world
*/
public @NotNull World getDestination() {
return this.destination;
}
@Override
public boolean isCancelled() {
return this.cancelled;
}
@Override
public void setCancelled(boolean cancel) {
this.cancelled = cancel;
}
@Override
public @NotNull HandlerList getHandlers() {
return HANDLERS;
}
@NotNull
public static HandlerList getHandlerList() {
return HANDLERS;
}
}

View File

@@ -0,0 +1,40 @@
package me.earthme.luminol.api.entity.player;
import org.apache.commons.lang3.Validate;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* A simple event fired when the respawn process of player is done
*/
public class PostPlayerRespawnEvent extends Event {
private static final HandlerList HANDLERS = new HandlerList();
private final Player player;
public PostPlayerRespawnEvent(Player player) {
Validate.notNull(player, "Player cannot be a null value!");
this.player = player;
}
/**
* Get the respawned player
* @return the player
*/
public @NotNull Player getPlayer() {
return this.player;
}
@Override
public @NotNull HandlerList getHandlers() {
return HANDLERS;
}
@NotNull
public static HandlerList getHandlerList() {
return HANDLERS;
}
}

View File

@@ -0,0 +1,35 @@
package me.earthme.luminol.api.portal;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* A event fired when an end platform is created.
*/
public class EndPlatformCreateEvent extends Event implements Cancellable {
private static final HandlerList HANDLERS = new HandlerList();
private boolean cancelled = false;
@Override
public boolean isCancelled() {
return this.cancelled;
}
@Override
public void setCancelled(boolean cancel) {
this.cancelled = cancel;
}
@Override
public @NotNull HandlerList getHandlers() {
return HANDLERS;
}
@NotNull
public static HandlerList getHandlerList() {
return HANDLERS;
}
}

View File

@@ -0,0 +1,53 @@
package me.earthme.luminol.api.portal;
import org.apache.commons.lang3.Validate;
import org.bukkit.Location;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* A event fired when the portal process started locating the destination position
* Notice: If you changed the destination to an another position in end teleportation.The end platform won't create under the entity and won't create
* if the position is out of current tick region
*/
public class PortalLocateEvent extends Event {
private static final HandlerList HANDLERS = new HandlerList();
private final Location original;
private final Location destination;
public PortalLocateEvent(Location original, Location destination) {
Validate.notNull(original, "original couldn't be null!");
Validate.notNull(destination, "destination couldn't be null!");
this.original = original;
this.destination = destination;
}
/**
* Get the destination position of this teleportation
* @return the destination position
*/
public Location getDestination() {
return this.destination;
}
/**
* Get the original portal position of this teleportation
* @return the original portal position
*/
public Location getOriginal() {
return this.original;
}
@Override
public @NotNull HandlerList getHandlers() {
return HANDLERS;
}
@NotNull
public static HandlerList getHandlerList() {
return HANDLERS;
}
}

View File

@@ -48,7 +48,7 @@
}
}
val log4jPlugins = sourceSets.create("log4jPlugins") {
@@ -153,7 +_,14 @@
@@ -153,12 +_,20 @@
}
dependencies {
@@ -64,6 +64,12 @@
implementation("ca.spottedleaf:concurrentutil:0.0.3")
implementation("org.jline:jline-terminal-ffm:3.27.1") // use ffm on java 22+
implementation("org.jline:jline-terminal-jni:3.27.1") // fall back to jni on java 21
implementation("net.minecrell:terminalconsoleappender:1.3.0")
implementation("net.kyori:adventure-text-serializer-ansi:4.21.0") // Keep in sync with adventureVersion from Paper-API build file
+ implementation("net.openhft:affinity:3.23.3") // Luminol
runtimeConfiguration(sourceSets.main.map { it.runtimeClasspath })
/*
@@ -217,26 +_,33 @@
implementation("me.lucko:spark-paper:1.10.133-20250413.112336-1")
}
@@ -104,14 +110,3 @@
"Build-Number" to (build ?: ""),
"Build-Time" to buildTime.toString(),
"Git-Branch" to gitBranch,
@@ -393,3 +_,10 @@
classpath(tasks.createReobfPaperclipJar.flatMap { it.outputZip })
mainClass.set(null as String?)
}
+
+// Pufferfish Start
+tasks.withType<JavaCompile> {
+ val compilerArgs = options.compilerArgs
+ compilerArgs.add("--add-modules=jdk.incubator.vector")
+}
+// Pufferfish End

View File

@@ -187,10 +187,10 @@ index bfd904e468bbf2cc1a5b3353d3a69ad5087c81ae..116933975ac975bb5a801be81e1c0e9b
+ // KioCG end
}
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index b5838a0320c729778f27f0d6a623eed4ef7c3a52..9122a78d08863cbc7321b7f7f2b6614e70dca846 100644
index 99301832bbb90f4ab00963f9062c54e829cc813b..46359300e533221cdc2d8aff9f9b98afe593c92b 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -5957,4 +5957,6 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -5961,4 +5961,6 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
return ((ServerLevel) this.level()).isPositionEntityTicking(this.blockPosition());
}
// Paper end - Expose entity id counter

View File

@@ -19,10 +19,10 @@ index ec4047312cf17f3ba91348ac8d71f747202bef87..56d00caf9db21798fdcbd6ec2cd84a84
);
});
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index b5838a0320c729778f27f0d6a623eed4ef7c3a52..18b04e13d0347935888b3f01fe35a39aeb49d94a 100644
index 46359300e533221cdc2d8aff9f9b98afe593c92b..cb347fe9e2876f3b26f004785c882f5faef560d7 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -4108,6 +4108,31 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -4112,6 +4112,31 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
}
// TODO any events that can modify go HERE
@@ -54,7 +54,7 @@ index b5838a0320c729778f27f0d6a623eed4ef7c3a52..18b04e13d0347935888b3f01fe35a39a
// check for same region
if (destination == this.level()) {
@@ -4224,7 +4249,18 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -4228,7 +4253,18 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
// we just select the spawn position
case END: {
if (destination.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.END) {
@@ -74,7 +74,7 @@ index b5838a0320c729778f27f0d6a623eed4ef7c3a52..18b04e13d0347935888b3f01fe35a39a
// need to load chunks so we can create the platform
destination.moonrise$loadChunksAsync(
targetPos, 16, // load 16 blocks to be safe from block physics
@@ -4245,7 +4281,18 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -4249,7 +4285,18 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
}
);
} else {
@@ -94,7 +94,7 @@ index b5838a0320c729778f27f0d6a623eed4ef7c3a52..18b04e13d0347935888b3f01fe35a39a
// need to load chunk for heightmap
destination.moonrise$loadChunksAsync(
spawnPos, 0,
@@ -4296,8 +4343,18 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -4300,8 +4347,18 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
WorldBorder destinationBorder = destination.getWorldBorder();
double dimensionScale = net.minecraft.world.level.dimension.DimensionType.getTeleportationScale(origin.dimensionType(), destination.dimensionType());
@@ -114,7 +114,7 @@ index b5838a0320c729778f27f0d6a623eed4ef7c3a52..18b04e13d0347935888b3f01fe35a39a
ca.spottedleaf.concurrentutil.completable.CallbackCompletable<BlockUtil.FoundRectangle> portalFound
= new ca.spottedleaf.concurrentutil.completable.CallbackCompletable<>();
@@ -4434,6 +4491,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -4438,6 +4495,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
if (!this.canPortalAsync(destination, takePassengers)) {
return false;
}
@@ -130,7 +130,7 @@ index b5838a0320c729778f27f0d6a623eed4ef7c3a52..18b04e13d0347935888b3f01fe35a39a
Vec3 initialPosition = this.position();
ChunkPos initialPositionChunk = new ChunkPos(
@@ -4501,6 +4567,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -4505,6 +4571,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
if (teleportComplete != null) {
teleportComplete.accept(teleported);
}

View File

@@ -10,10 +10,10 @@ VMP (https://github.com/RelativityMC/VMP-fabric)
Licensed under: MIT (https://opensource.org/licenses/MIT)
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index 18b04e13d0347935888b3f01fe35a39aeb49d94a..97ef9c15618168a9b4f169a03f15d4e3e326f4f6 100644
index cb347fe9e2876f3b26f004785c882f5faef560d7..95fd1fc621a01d4a2a97e78f471a1d1a599db612 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -1081,7 +1081,14 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -1085,7 +1085,14 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
private double moveStartZ;
// Paper end - detailed watchdog information
@@ -28,7 +28,7 @@ index 18b04e13d0347935888b3f01fe35a39aeb49d94a..97ef9c15618168a9b4f169a03f15d4e3
final Vec3 originalMovement = movement; // Paper - Expose pre-collision velocity
// Paper start - detailed watchdog information
ca.spottedleaf.moonrise.common.util.TickThread.ensureTickThread("Cannot move an entity off-main");
@@ -5048,6 +5055,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -5052,6 +5059,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
}
public final void setBoundingBox(AABB bb) {

View File

@@ -8,10 +8,10 @@ As part of: Kaiiju (https://github.com/KaiijuMC/Kaiiju/blob/c2b7aec8f7b418a39a2e
Licensed under: GPL-3.0 (https://github.com/KaiijuMC/Kaiiju/blob/c2b7aec8f7b418a39a2ec408e6411e6f752379da/LICENSE)
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index 97ef9c15618168a9b4f169a03f15d4e3e326f4f6..96790ead67c29013302341422fd23d2cefd720bf 100644
index 95fd1fc621a01d4a2a97e78f471a1d1a599db612..75b5856b892272aaa70616c35dea75ba3ac76058 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -4273,14 +4273,18 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -4277,14 +4277,18 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
targetPos, 16, // load 16 blocks to be safe from block physics
ca.spottedleaf.concurrentutil.util.Priority.HIGH,
(chunks) -> {
@@ -34,7 +34,7 @@ index 97ef9c15618168a9b4f169a03f15d4e3e326f4f6..96790ead67c29013302341422fd23d2c
TeleportTransition.PLAY_PORTAL_SOUND.then(TeleportTransition.PLACE_PORTAL_TICKET),
org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.END_PORTAL
)
@@ -4306,11 +4310,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -4310,11 +4314,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
ca.spottedleaf.concurrentutil.util.Priority.HIGH,
(chunks) -> {
BlockPos adjustedSpawn = destination.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, spawnPos);
@@ -52,7 +52,7 @@ index 97ef9c15618168a9b4f169a03f15d4e3e326f4f6..96790ead67c29013302341422fd23d2c
Relative.union(Relative.DELTA, Relative.ROTATION),
TeleportTransition.PLAY_PORTAL_SOUND.then(TeleportTransition.PLACE_PORTAL_TICKET),
org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.END_PORTAL
@@ -4507,6 +4515,10 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -4511,6 +4519,10 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
return false;
}
// Luminol end
@@ -63,7 +63,7 @@ index 97ef9c15618168a9b4f169a03f15d4e3e326f4f6..96790ead67c29013302341422fd23d2c
Vec3 initialPosition = this.position();
ChunkPos initialPositionChunk = new ChunkPos(
@@ -4571,8 +4583,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -4575,8 +4587,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
info.postTeleportTransition().onTransition(teleported);
}

View File

@@ -46,7 +46,7 @@ index 8329bc0cf531a1317ff8e213e948019d28df1eea..84a6bf575902676fc06211562b578064
} else {entity.inactiveTick();} // Paper - EAR 2
profilerFiller.pop();
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index 96790ead67c29013302341422fd23d2cefd720bf..416d096cb4dfafeea625e70cdb420669442fdea9 100644
index 75b5856b892272aaa70616c35dea75ba3ac76058..cf634aa6898ccdb53a34a410266aeecb86c5f0de 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -350,6 +350,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -57,7 +57,7 @@ index 96790ead67c29013302341422fd23d2cefd720bf..416d096cb4dfafeea625e70cdb420669
public void inactiveTick() {
}
@@ -3223,6 +3224,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -3227,6 +3228,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
} else {
if (this.portalProcess == null || !this.portalProcess.isSamePortal(portal)) {
this.portalProcess = new PortalProcessor(portal, pos.immutable());

View File

@@ -6,10 +6,10 @@ Subject: [PATCH] Fix off tickregion sync teleport
Folis's teleportAsync implementation has some checks missing during the sync teleportation checks, if we are teleport to the edge of the tickregion, it is still asserting that we are in the same tickregion and moved us directly, but there is actually some logics is already touching the stuff out of current tickregion.So we added some new edge checks to the sync teleportation checks which will check the tickregion belonging in a shape of cycle which is in min(entity's bounding box + simulate distance, 6) of radius to fix that issue
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index 416d096cb4dfafeea625e70cdb420669442fdea9..dbc8d2729782dd073a03682d4e8c96c4e03ce1e6 100644
index cf634aa6898ccdb53a34a410266aeecb86c5f0de..4743de5c39ee52bdde1f70e340d547a6cc7f4ead 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -4024,6 +4024,21 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -4028,6 +4028,21 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
this.resetStoredPositions();
}
@@ -31,7 +31,7 @@ index 416d096cb4dfafeea625e70cdb420669442fdea9..dbc8d2729782dd073a03682d4e8c96c4
protected final void transform(TeleportTransition telpeort) {
PositionMoveRotation move = PositionMoveRotation.calculateAbsolute(
PositionMoveRotation.of(this), PositionMoveRotation.of(telpeort), telpeort.relatives()
@@ -4146,7 +4161,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -4150,7 +4165,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
// check for same region
if (destination == this.level()) {
Vec3 currPos = this.position();

View File

@@ -6,10 +6,10 @@ Subject: [PATCH] Teleport async if entity was moving to another region at once
On folia, entity usually cannot move out of the tickregion, but sometimes it actually does(like some end pearl gun that can shoot an end pearl to the block faraway than 10000 blocks even more). To fix this, we added a temporary fix which teleport these entities to the destination instead running its move logics so that we could ensure anything is under control.But one thing need to consider is that teleportAsync is actually calling halfway of the entity tick and there is still something running when teleportAsync called, which is actually modified the entity in another thread, so there is still need an improvement
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
index dbc8d2729782dd073a03682d4e8c96c4e03ce1e6..a33994df200a93d7b9721cbe13d898d96af70844 100644
index 4743de5c39ee52bdde1f70e340d547a6cc7f4ead..a023cc399fc8bfee7771e5ca6716578f89b7072f 100644
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -1084,6 +1084,10 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -1088,6 +1088,10 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
private boolean boundingBoxChanged = false; // Gale - VMP - skip entity move if movement is zero
@@ -20,7 +20,7 @@ index dbc8d2729782dd073a03682d4e8c96c4e03ce1e6..a33994df200a93d7b9721cbe13d898d9
public void move(MoverType type, Vec3 movement) {
// Gale start - VMP - skip entity move if movement is zero
if (!this.boundingBoxChanged && movement.equals(Vec3.ZERO)) {
@@ -1099,6 +1103,32 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
@@ -1103,6 +1107,32 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
this.moveStartZ = this.getZ();
this.moveVector = movement;
}

View File

@@ -0,0 +1,39 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <mrhua269@gmail.com>
Date: Sun, 15 Jun 2025 16:58:05 +0800
Subject: [PATCH] Do not fire pre creature spawn event unless some plugin is
listening it
diff --git a/net/minecraft/world/level/NaturalSpawner.java b/net/minecraft/world/level/NaturalSpawner.java
index f324a74a191d3fe3e270556d07c4543ec34e0195..2fb36cba4cda3f7b84efae9cba6bed2394fb0457 100644
--- a/net/minecraft/world/level/NaturalSpawner.java
+++ b/net/minecraft/world/level/NaturalSpawner.java
@@ -360,16 +360,18 @@ public final class NaturalSpawner {
) {
EntityType<?> entityType = data.type();
// Paper start - PreCreatureSpawnEvent
- com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent(
- org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, level),
- org.bukkit.craftbukkit.entity.CraftEntityType.minecraftToBukkit(entityType), org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL
- );
- if (!event.callEvent()) {
- if (event.shouldAbortSpawn()) {
- return PreSpawnStatus.ABORT;
+ if (com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent.getHandlerList().getRegisteredListeners().length != 0) { // Luminol - Do not fire pre creature spawn event unless some plugin is listening it
+ com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent(
+ org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, level),
+ org.bukkit.craftbukkit.entity.CraftEntityType.minecraftToBukkit(entityType), org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL
+ );
+ if (!event.callEvent()) {
+ if (event.shouldAbortSpawn()) {
+ return PreSpawnStatus.ABORT;
+ }
+ return PreSpawnStatus.CANCELLED;
}
- return PreSpawnStatus.CANCELLED;
- }
+ } // Luminol - Do not fire pre creature spawn event unless some plugin is listening it
final boolean success = entityType.getCategory() != MobCategory.MISC
// Paper end - PreCreatureSpawnEvent
&& (

View File

@@ -1,26 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <mrhua269@gmail.com>
Date: Thu, 5 Jun 2025 21:11:31 +0800
Subject: [PATCH] Fix off region thrown egg new entity creating
should set pos before so that we could correctly modify the entity's other attribute on-region without triggering the async catchers
diff --git a/net/minecraft/world/entity/projectile/ThrownEgg.java b/net/minecraft/world/entity/projectile/ThrownEgg.java
index 73ec34b43f3fb2aa3edc3f1cb48a923d1fa32036..5760ae39e8c452b8291353ed59ce7f8ef4d43dc1 100644
--- a/net/minecraft/world/entity/projectile/ThrownEgg.java
+++ b/net/minecraft/world/entity/projectile/ThrownEgg.java
@@ -97,12 +97,13 @@ public class ThrownEgg extends ThrowableItemProjectile {
for (int i1 = 0; i1 < i; i1++) {
net.minecraft.world.entity.Entity chicken = newEntityType.create(this.level(), net.minecraft.world.entity.EntitySpawnReason.TRIGGERED); // CraftBukkit
if (chicken != null) {
+ chicken.snapTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), 0.0F); // Luminol - Fix off region thrown egg - move up
// CraftBukkit start
if (chicken.getBukkitEntity() instanceof org.bukkit.entity.Ageable ageable) {
ageable.setBaby();
}
// CraftBukkit end
- chicken.snapTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), 0.0F);
+ // chicken.snapTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), 0.0F); // Luminol - Fix off region thrown egg - move up
// CraftBukkit start
if (chicken instanceof Chicken realChicken) {
Optional.ofNullable(this.getItem().get(DataComponents.CHICKEN_VARIANT))

View File

@@ -0,0 +1,36 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: HaHaWTH <102713261+HaHaWTH@users.noreply.github.com>
Date: Tue, 9 Nov 2077 00:00:00 +0800
Subject: [PATCH] Cpu affinity
diff --git a/io/papermc/paper/threadedregions/TickRegionScheduler.java b/io/papermc/paper/threadedregions/TickRegionScheduler.java
index fa6b8d756195c1b430cc11214a901bd42eebc98d..0357792de0ed8ec9058d1847c8b45c33ff365af6 100644
--- a/io/papermc/paper/threadedregions/TickRegionScheduler.java
+++ b/io/papermc/paper/threadedregions/TickRegionScheduler.java
@@ -49,6 +49,25 @@ public final class TickRegionScheduler {
@Override
public Thread newThread(final Runnable run) {
+ // Luminol start - cpu affinity
+ if (me.earthme.luminol.config.modules.misc.CpuAffinityConfig.cpuAffinityEnabled) {
+ Runnable affinityRunnable = new Runnable() {
+ private boolean affinitySet = false;
+
+ @Override
+ public void run() {
+ if (!this.affinitySet) {
+ this.affinitySet = true;
+ net.openhft.affinity.Affinity.setAffinity(me.earthme.luminol.config.modules.misc.CpuAffinityConfig.tickRegionAffinityBitSet);
+ }
+ run.run();
+ }
+ };
+ final Thread ret = new TickThreadRunner(affinityRunnable, "Region Scheduler Thread #" + this.idGenerator.getAndIncrement());
+ ret.setUncaughtExceptionHandler(TickRegionScheduler.this::uncaughtException);
+ return ret;
+ }
+ // Luminol end - cpu affinity
final Thread ret = new TickThreadRunner(run, "Region Scheduler Thread #" + this.idGenerator.getAndIncrement());
ret.setUncaughtExceptionHandler(TickRegionScheduler.this::uncaughtException);
return ret;

View File

@@ -0,0 +1,21 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Helvetica Volubi <suisuroru@blue-millennium.fun>
Date: Wed, 18 Jun 2025 23:46:20 +0800
Subject: [PATCH] Temporarily fix teleport yam and pitch
diff --git a/net/minecraft/server/commands/TeleportCommand.java b/net/minecraft/server/commands/TeleportCommand.java
index 174122905addbc88e818cd4946e831aec051b91a..4451f79df614c6e57e813996888b4e99a14c87d5 100644
--- a/net/minecraft/server/commands/TeleportCommand.java
+++ b/net/minecraft/server/commands/TeleportCommand.java
@@ -283,8 +283,8 @@ public class TeleportCommand {
if (true) {
ServerLevel worldFinal = level;
Vec3 posFinal = new Vec3(x, y, z);
- Float yawFinal = Float.valueOf(f);
- Float pitchFinal = Float.valueOf(f1);
+ Float yawFinal = Float.valueOf(f + target.getYRot());
+ Float pitchFinal = Float.valueOf(f1 + target.getXRot());
target.getBukkitEntity().taskScheduler.schedule((Entity nmsEntity) -> {
nmsEntity.unRide();
nmsEntity.teleportAsync(

View File

@@ -18,7 +18,7 @@ index a0b84535a9d3833d4df692b85b272f145559dd80..c2ba46408b5ad727d7a17f21d47b2898
return;
}
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index 1091d2747c04166447540b37d86f51fe2591adc8..e0cee79b949fd2a4684bdfe7aa257c2ea96914b0 100644
index 7c30289ff28c4f0b91597da9c4aa192e7ff559cc..51543cc35e9158c3c083f4082304ecd4da5cf0a2 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -314,7 +314,7 @@ public final class CraftServer implements Server {

View File

@@ -23,7 +23,7 @@ index 631bec0adee5b01bfb931c25195b949eaf2efd27..05d364bcadb137af4e1a8c955643b7dc
@Override
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index e0cee79b949fd2a4684bdfe7aa257c2ea96914b0..047369190b8d03c5765595ba898da3a5c463a6c0 100644
index 51543cc35e9158c3c083f4082304ecd4da5cf0a2..e86347245b674b552a8d9e5a109ec8a41999f7ee 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -1392,7 +1392,11 @@ public final class CraftServer implements Server {

View File

@@ -1,16 +1,16 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MrHua269 <wangxyper@163.com>
Date: Tue, 11 Feb 2025 12:01:39 +0800
From: MrHua269 <mrhua269@gmail.com>
Date: Thu, 12 Jun 2025 07:59:03 +0800
Subject: [PATCH] Purpur Lobotomize stuck villagers
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java
index e86f69f75d406b81d9ca32f9cad5e31cb8c55b54..9ca1d60cb7f3e65cdf491bd65c1c0c38cd2c73e8 100644
index 2ec652c1675a999d7cf157a5a002aba9d58afa0d..49bfeb81bdc998afc4aa55939840ac75397d8530 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java
@@ -381,4 +381,11 @@ public class CraftVillager extends CraftAbstractVillager implements Villager {
public void clearReputations() {
getHandle().getGossips().gossips.clear();
@@ -391,4 +391,11 @@ public class CraftVillager extends CraftAbstractVillager implements Villager {
public void restock() {
getHandle().restock();
}
+
+ // Purpur start

View File

@@ -1,44 +0,0 @@
--- /dev/null
+++ b/src/main/java/abomination/IRegionFile.java
@@ -1,0 +_,41 @@
+package abomination;
+
+import ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemRegionFile;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.level.ChunkPos;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.file.Path;
+
+public interface IRegionFile extends ChunkSystemRegionFile, AutoCloseable {
+ Path getPath();
+
+ DataInputStream getChunkDataInputStream(ChunkPos pos) throws IOException;
+
+ boolean doesChunkExist(ChunkPos pos) throws Exception;
+
+ DataOutputStream getChunkDataOutputStream(ChunkPos pos) throws IOException;
+
+ void flush() throws IOException;
+
+ void clear(ChunkPos pos) throws IOException;
+
+ boolean hasChunk(ChunkPos pos);
+
+ void close() throws IOException;
+
+ void write(ChunkPos pos, ByteBuffer buf) throws IOException;
+
+ CompoundTag getOversizedData(int x, int z) throws IOException;
+
+ boolean isOversized(int x, int z);
+
+ boolean recalculateHeader() throws IOException;
+
+ void setOversized(int x, int z, boolean oversized) throws IOException;
+
+ default int getRecalculateCount() {return 0;} // Luminol - Configurable region file format
+}

View File

@@ -1,625 +0,0 @@
--- /dev/null
+++ b/src/main/java/abomination/LinearRegionFile.java
@@ -1,0 +_,622 @@
+package abomination;
+
+import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO;
+import com.github.luben.zstd.ZstdInputStream;
+import com.github.luben.zstd.ZstdOutputStream;
+import com.mojang.logging.LogUtils;
+import net.jpountz.lz4.LZ4Compressor;
+import net.jpountz.lz4.LZ4Factory;
+import net.jpountz.lz4.LZ4FastDecompressor;
+import net.openhft.hashing.LongHashFunction;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.level.chunk.storage.RegionStorageInfo;
+import net.minecraft.world.level.chunk.storage.RegionFileVersion;
+import net.minecraft.world.level.ChunkPos;
+import org.slf4j.Logger;
+
+import javax.annotation.Nullable;
+import java.io.*;
+import java.nio.ByteBuffer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.LockSupport;
+import java.util.concurrent.locks.ReentrantLock;
+
+// LinearRegionFile_implementation_version_0_5byXymb
+// Just gonna use this string to inform other forks about updates ;-)
+public class LinearRegionFile implements IRegionFile{
+ private static final long SUPERBLOCK = 0xc3ff13183cca9d9aL;
+ private static final byte VERSION = 3;
+ private static final int HEADER_SIZE = 27;
+ private static final int FOOTER_SIZE = 8;
+ private static final Logger LOGGER = LogUtils.getLogger();
+
+ private byte[][] bucketBuffers;
+ private final byte[][] buffer = new byte[1024][];
+ private final int[] bufferUncompressedSize = new int[1024];
+
+ private final long[] chunkTimestamps = new long[1024];
+ private final Object markedToSaveLock = new Object();
+
+ private final LZ4Compressor compressor;
+ private final LZ4FastDecompressor decompressor;
+
+ private boolean markedToSave = false;
+ private boolean close = false;
+
+ public final ReentrantLock fileLock = new ReentrantLock(true);
+ public Path regionFile;
+
+ private final int compressionLevel;
+ private int gridSize = 8;
+ private int bucketSize = 4;
+ private final Thread bindThread;
+
+ public Path getRegionFile() {
+ return this.regionFile;
+ }
+
+ public ReentrantLock getFileLock() {
+ return this.fileLock;
+ }
+
+ private int chunkToBucketIdx(int chunkX, int chunkZ) {
+ int bx = chunkX / bucketSize, bz = chunkZ / bucketSize;
+ return bx * gridSize + bz;
+ }
+
+ private void openBucket(int chunkX, int chunkZ) {
+ chunkX = Math.floorMod(chunkX, 32);
+ chunkZ = Math.floorMod(chunkZ, 32);
+ int idx = chunkToBucketIdx(chunkX, chunkZ);
+
+ if (bucketBuffers == null) return;
+ if (bucketBuffers[idx] != null) {
+ try {
+ ByteArrayInputStream bucketByteStream = new ByteArrayInputStream(bucketBuffers[idx]);
+ ZstdInputStream zstdStream = new ZstdInputStream(bucketByteStream);
+ ByteBuffer bucketBuffer = ByteBuffer.wrap(zstdStream.readAllBytes());
+
+ int bx = chunkX / bucketSize, bz = chunkZ / bucketSize;
+
+ for (int cx = 0; cx < 32 / gridSize; cx++) {
+ for (int cz = 0; cz < 32 / gridSize; cz++) {
+ int chunkIndex = (bx * (32 / gridSize) + cx) + (bz * (32 / gridSize) + cz) * 32;
+
+ int chunkSize = bucketBuffer.getInt();
+ long timestamp = bucketBuffer.getLong();
+ this.chunkTimestamps[chunkIndex] = timestamp;
+
+ if (chunkSize > 0) {
+ byte[] chunkData = new byte[chunkSize - 8];
+ bucketBuffer.get(chunkData);
+
+ int maxCompressedLength = this.compressor.maxCompressedLength(chunkData.length);
+ byte[] compressed = new byte[maxCompressedLength];
+ int compressedLength = this.compressor.compress(chunkData, 0, chunkData.length, compressed, 0, maxCompressedLength);
+ byte[] finalCompressed = new byte[compressedLength];
+ System.arraycopy(compressed, 0, finalCompressed, 0, compressedLength);
+
+ // TODO: Optimization - return the requested chunk immediately to save on one LZ4 decompression
+ this.buffer[chunkIndex] = finalCompressed;
+ this.bufferUncompressedSize[chunkIndex] = chunkData.length;
+ }
+ }
+ }
+ } catch (IOException ex) {
+ throw new RuntimeException("Region file corrupted: " + regionFile + " bucket: " + idx);
+ // TODO: Make sure the server crashes instead of corrupting the world
+ }
+ bucketBuffers[idx] = null;
+ }
+ }
+
+ public boolean regionFileOpen = false;
+
+ private synchronized void openRegionFile() {
+ if (regionFileOpen) return;
+ regionFileOpen = true;
+
+ File regionFile = new File(this.regionFile.toString());
+
+ if(!regionFile.canRead()) {
+ this.bindThread.start();
+ return;
+ }
+
+ try {
+ byte[] fileContent = Files.readAllBytes(this.regionFile);
+ ByteBuffer buffer = ByteBuffer.wrap(fileContent);
+
+ long superBlock = buffer.getLong();
+ if (superBlock != SUPERBLOCK)
+ throw new RuntimeException("Invalid superblock: " + superBlock + " file " + this.regionFile);
+
+ byte version = buffer.get();
+ if (version == 1 || version == 2) {
+ parseLinearV1(buffer);
+ } else if (version == 3) {
+ parseLinearV2(buffer);
+ } else {
+ throw new RuntimeException("Invalid version: " + version + " file " + this.regionFile);
+ }
+
+ this.bindThread.start();
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to open region file " + this.regionFile, e);
+ }
+ }
+
+ private void parseLinearV1(ByteBuffer buffer) throws IOException {
+ final int HEADER_SIZE = 32;
+ final int FOOTER_SIZE = 8;
+
+ // Skip newestTimestamp (Long) + Compression level (Byte) + Chunk count (Short): Unused.
+ buffer.position(buffer.position() + 11);
+
+ int dataCount = buffer.getInt();
+ long fileLength = this.regionFile.toFile().length();
+ if (fileLength != HEADER_SIZE + dataCount + FOOTER_SIZE) {
+ throw new IOException("Invalid file length: " + this.regionFile + " " + fileLength + " " + (HEADER_SIZE + dataCount + FOOTER_SIZE));
+ }
+
+ buffer.position(buffer.position() + 8); // Skip data hash (Long): Unused.
+
+ byte[] rawCompressed = new byte[dataCount];
+ buffer.get(rawCompressed);
+
+ ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(rawCompressed);
+ ZstdInputStream zstdInputStream = new ZstdInputStream(byteArrayInputStream);
+ ByteBuffer decompressedBuffer = ByteBuffer.wrap(zstdInputStream.readAllBytes());
+
+ int[] starts = new int[1024];
+ for (int i = 0; i < 1024; i++) {
+ starts[i] = decompressedBuffer.getInt();
+ decompressedBuffer.getInt(); // Skip timestamps (Int): Unused.
+ }
+
+ for (int i = 0; i < 1024; i++) {
+ if (starts[i] > 0) {
+ int size = starts[i];
+ byte[] chunkData = new byte[size];
+ decompressedBuffer.get(chunkData);
+
+ int maxCompressedLength = this.compressor.maxCompressedLength(size);
+ byte[] compressed = new byte[maxCompressedLength];
+ int compressedLength = this.compressor.compress(chunkData, 0, size, compressed, 0, maxCompressedLength);
+ byte[] finalCompressed = new byte[compressedLength];
+ System.arraycopy(compressed, 0, finalCompressed, 0, compressedLength);
+
+ this.buffer[i] = finalCompressed;
+ this.bufferUncompressedSize[i] = size;
+ this.chunkTimestamps[i] = getTimestamp(); // Use current timestamp as we don't have the original
+ }
+ }
+ }
+
+ private void parseLinearV2(ByteBuffer buffer) throws IOException {
+ buffer.getLong(); // Skip newestTimestamp (Long)
+ gridSize = buffer.get();
+ if (gridSize != 1 && gridSize != 2 && gridSize != 4 && gridSize != 8 && gridSize != 16 && gridSize != 32)
+ throw new RuntimeException("Invalid grid size: " + gridSize + " file " + this.regionFile);
+ bucketSize = 32 / gridSize;
+
+ buffer.getInt(); // Skip region_x (Int)
+ buffer.getInt(); // Skip region_z (Int)
+
+ boolean[] chunkExistenceBitmap = deserializeExistenceBitmap(buffer);
+
+ while (true) {
+ byte featureNameLength = buffer.get();
+ if (featureNameLength == 0) break;
+ byte[] featureNameBytes = new byte[featureNameLength];
+ buffer.get(featureNameBytes);
+ String featureName = new String(featureNameBytes);
+ int featureValue = buffer.getInt();
+ // System.out.println("NBT Feature: " + featureName + " = " + featureValue);
+ }
+
+ int[] bucketSizes = new int[gridSize * gridSize];
+ byte[] bucketCompressionLevels = new byte[gridSize * gridSize];
+ long[] bucketHashes = new long[gridSize * gridSize];
+ for (int i = 0; i < gridSize * gridSize; i++) {
+ bucketSizes[i] = buffer.getInt();
+ bucketCompressionLevels[i] = buffer.get();
+ bucketHashes[i] = buffer.getLong();
+ }
+
+ bucketBuffers = new byte[gridSize * gridSize][];
+ for (int i = 0; i < gridSize * gridSize; i++) {
+ if (bucketSizes[i] > 0) {
+ bucketBuffers[i] = new byte[bucketSizes[i]];
+ buffer.get(bucketBuffers[i]);
+ long rawHash = LongHashFunction.xx().hashBytes(bucketBuffers[i]);
+ if (rawHash != bucketHashes[i]) throw new IOException("Region file hash incorrect " + this.regionFile);
+ }
+ }
+
+ long footerSuperBlock = buffer.getLong();
+ if (footerSuperBlock != SUPERBLOCK)
+ throw new IOException("Footer superblock invalid " + this.regionFile);
+ }
+
+ public LinearRegionFile(RegionStorageInfo storageKey, Path directory, Path path, boolean dsync, int compressionLevel) throws IOException {
+ this(storageKey, directory, path, RegionFileVersion.getCompressionFormat(), dsync, compressionLevel);
+ }
+
+ public LinearRegionFile(RegionStorageInfo storageKey, Path path, Path directory, RegionFileVersion compressionFormat, boolean dsync, int compressionLevel) throws IOException {
+ Runnable flushCheck = () -> {
+ while (!close) {
+ synchronized (saveLock) {
+ if (markedToSave && activeSaveThreads < SAVE_THREAD_MAX_COUNT) {
+ activeSaveThreads++;
+ Runnable flushOperation = () -> {
+ try {
+ flush();
+ } catch (IOException ex) {
+ LOGGER.error("Region file {} flush failed", this.regionFile.toAbsolutePath(), ex);
+ } finally {
+ synchronized (saveLock) {
+ activeSaveThreads--;
+ }
+ }
+ };
+
+ Thread saveThread = USE_VIRTUAL_THREAD ?
+ Thread.ofVirtual().name("Linear IO - " + LinearRegionFile.this.hashCode()).unstarted(flushOperation) :
+ Thread.ofPlatform().name("Linear IO - " + LinearRegionFile.this.hashCode()).unstarted(flushOperation);
+ saveThread.setPriority(Thread.NORM_PRIORITY - 3);
+ saveThread.start();
+ }
+ }
+ LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(SAVE_DELAY_MS));
+ }
+ };
+ this.bindThread = USE_VIRTUAL_THREAD ? Thread.ofVirtual().unstarted(flushCheck) : Thread.ofPlatform().unstarted(flushCheck);
+ this.bindThread.setName("Linear IO Schedule - " + this.hashCode());
+ this.regionFile = path;
+ this.compressionLevel = compressionLevel;
+
+ this.compressor = LZ4Factory.fastestInstance().fastCompressor();
+ this.decompressor = LZ4Factory.fastestInstance().fastDecompressor();
+ }
+
+ private synchronized void markToSave() {
+ synchronized(markedToSaveLock) {
+ markedToSave = true;
+ }
+ }
+
+ private synchronized boolean isMarkedToSave() {
+ synchronized(markedToSaveLock) {
+ if(markedToSave) {
+ markedToSave = false;
+ return true;
+ }
+ return false;
+ }
+ }
+
+ public static int SAVE_THREAD_MAX_COUNT = 6;
+ public static int SAVE_DELAY_MS = 100;
+ public static boolean USE_VIRTUAL_THREAD = true;
+ private static final Object saveLock = new Object();
+ private static int activeSaveThreads = 0;
+
+ /*public void run() {
+ while (!close) {
+ synchronized (saveLock) {
+ if (markedToSave && activeSaveThreads < SAVE_THREAD_MAX_COUNT) {
+ activeSaveThreads++;
+ Thread saveThread = new Thread(() -> {
+ try {
+ flush();
+ } catch (IOException ex) {
+ LOGGER.error("Region file " + this.regionFile.toAbsolutePath() + " flush failed", ex);
+ } finally {
+ synchronized (saveLock) {
+ activeSaveThreads--;
+ }
+ }
+ }, "RegionFileFlush");
+ saveThread.setPriority(Thread.NORM_PRIORITY - 3);
+ saveThread.start();
+ }
+ }
+ LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(SAVE_DELAY_MS));
+ }
+ }*/
+
+ public synchronized boolean doesChunkExist(ChunkPos pos) throws Exception {
+ openRegionFile();
+ throw new Exception("doesChunkExist is a stub");
+ }
+
+ public synchronized void flush() throws IOException {
+ if(!isMarkedToSave()) return;
+
+ openRegionFile();
+
+ long timestamp = getTimestamp();
+
+long writeStart = System.nanoTime();
+ File tempFile = new File(regionFile.toString() + ".tmp");
+ FileOutputStream fileStream = new FileOutputStream(tempFile);
+ DataOutputStream dataStream = new DataOutputStream(fileStream);
+
+ dataStream.writeLong(SUPERBLOCK);
+ dataStream.writeByte(VERSION);
+ dataStream.writeLong(timestamp);
+ dataStream.writeByte(gridSize);
+
+ String fileName = regionFile.getFileName().toString();
+ String[] parts = fileName.split("\\.");
+ int regionX = 0;
+ int regionZ = 0;
+ try {
+ if (parts.length >= 4) {
+ regionX = Integer.parseInt(parts[1]);
+ regionZ = Integer.parseInt(parts[2]);
+ } else {
+ LOGGER.warn("Unexpected file name format: " + fileName);
+ }
+ } catch (NumberFormatException e) {
+ LOGGER.error("Failed to parse region coordinates from file name: " + fileName, e);
+ }
+
+ dataStream.writeInt(regionX);
+ dataStream.writeInt(regionZ);
+
+ boolean[] chunkExistenceBitmap = new boolean[1024];
+ for (int i = 0; i < 1024; i++) {
+ chunkExistenceBitmap[i] = (this.bufferUncompressedSize[i] > 0);
+ }
+ writeSerializedExistenceBitmap(dataStream, chunkExistenceBitmap);
+
+ writeNBTFeatures(dataStream);
+
+ int bucketMisses = 0;
+ byte[][] buckets = new byte[gridSize * gridSize][];
+ for (int bx = 0; bx < gridSize; bx++) {
+ for (int bz = 0; bz < gridSize; bz++) {
+ if (bucketBuffers != null && bucketBuffers[bx * gridSize + bz] != null) {
+ buckets[bx * gridSize + bz] = bucketBuffers[bx * gridSize + bz];
+ continue;
+ }
+ bucketMisses++;
+
+ ByteArrayOutputStream bucketStream = new ByteArrayOutputStream();
+ ZstdOutputStream zstdStream = new ZstdOutputStream(bucketStream, this.compressionLevel);
+ DataOutputStream bucketDataStream = new DataOutputStream(zstdStream);
+
+ boolean hasData = false;
+ for (int cx = 0; cx < 32 / gridSize; cx++) {
+ for (int cz = 0; cz < 32 / gridSize; cz++) {
+ int chunkIndex = (bx * 32 / gridSize + cx) + (bz * 32 / gridSize + cz) * 32;
+ if (this.bufferUncompressedSize[chunkIndex] > 0) {
+ hasData = true;
+ byte[] chunkData = new byte[this.bufferUncompressedSize[chunkIndex]];
+ this.decompressor.decompress(this.buffer[chunkIndex], 0, chunkData, 0, this.bufferUncompressedSize[chunkIndex]);
+ bucketDataStream.writeInt(chunkData.length + 8);
+ bucketDataStream.writeLong(this.chunkTimestamps[chunkIndex]);
+ bucketDataStream.write(chunkData);
+ } else {
+ bucketDataStream.writeInt(0);
+ bucketDataStream.writeLong(this.chunkTimestamps[chunkIndex]);
+ }
+ }
+ }
+ bucketDataStream.close();
+
+ if (hasData) {
+ buckets[bx * gridSize + bz] = bucketStream.toByteArray();
+ }
+ }
+ }
+
+ for (int i = 0; i < gridSize * gridSize; i++) {
+ dataStream.writeInt(buckets[i] != null ? buckets[i].length : 0);
+ dataStream.writeByte(this.compressionLevel);
+ long rawHash = 0;
+ if (buckets[i] != null) {
+ rawHash = LongHashFunction.xx().hashBytes(buckets[i]);
+ }
+ dataStream.writeLong(rawHash);
+ }
+
+ for (int i = 0; i < gridSize * gridSize; i++) {
+ if (buckets[i] != null) {
+ dataStream.write(buckets[i]);
+ }
+ }
+
+ dataStream.writeLong(SUPERBLOCK);
+
+ dataStream.flush();
+ fileStream.getFD().sync();
+ fileStream.getChannel().force(true); // Ensure atomicity on Btrfs
+ dataStream.close();
+
+ fileStream.close();
+ Files.move(tempFile.toPath(), this.regionFile, StandardCopyOption.REPLACE_EXISTING);
+//System.out.println("writeStart REGION FILE FLUSH " + (System.nanoTime() - writeStart) + " misses: " + bucketMisses);
+ }
+
+ private void writeNBTFeatures(DataOutputStream dataStream) throws IOException {
+ // writeNBTFeature(dataStream, "example", 1);
+ dataStream.writeByte(0); // End of NBT features
+ }
+
+ private void writeNBTFeature(DataOutputStream dataStream, String featureName, int featureValue) throws IOException {
+ byte[] featureNameBytes = featureName.getBytes();
+ dataStream.writeByte(featureNameBytes.length);
+ dataStream.write(featureNameBytes);
+ dataStream.writeInt(featureValue);
+ }
+
+ public static final int MAX_CHUNK_SIZE = 500 * 1024 * 1024; // Abomination - prevent chunk dupe
+
+ public synchronized void write(ChunkPos pos, ByteBuffer buffer) {
+ openRegionFile();
+ openBucket(pos.x, pos.z);
+ try {
+ byte[] b = toByteArray(new ByteArrayInputStream(buffer.array()));
+ int uncompressedSize = b.length;
+
+ if (uncompressedSize > MAX_CHUNK_SIZE) {
+ LOGGER.error("Chunk dupe attempt " + this.regionFile);
+ clear(pos);
+ } else {
+ int maxCompressedLength = this.compressor.maxCompressedLength(b.length);
+ byte[] compressed = new byte[maxCompressedLength];
+ int compressedLength = this.compressor.compress(b, 0, b.length, compressed, 0, maxCompressedLength);
+ b = new byte[compressedLength];
+ System.arraycopy(compressed, 0, b, 0, compressedLength);
+
+ int index = getChunkIndex(pos.x, pos.z);
+ this.buffer[index] = b;
+ this.chunkTimestamps[index] = getTimestamp();
+ this.bufferUncompressedSize[getChunkIndex(pos.x, pos.z)] = uncompressedSize;
+ }
+ } catch (IOException e) {
+ LOGGER.error("Chunk write IOException " + e + " " + this.regionFile);
+ }
+ markToSave();
+ }
+
+ public DataOutputStream getChunkDataOutputStream(ChunkPos pos) {
+ openRegionFile();
+ openBucket(pos.x, pos.z);
+ return new DataOutputStream(new BufferedOutputStream(new LinearRegionFile.ChunkBuffer(pos)));
+ }
+
+ @Override
+ public MoonriseRegionFileIO.RegionDataController.WriteData moonrise$startWrite(CompoundTag data, ChunkPos pos) throws IOException {
+ final DataOutputStream out = this.getChunkDataOutputStream(pos);
+
+ return new ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData(
+ data, ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData.WriteResult.WRITE,
+ out, regionFile -> out.close()
+ );
+ }
+
+ private class ChunkBuffer extends ByteArrayOutputStream {
+
+ private final ChunkPos pos;
+
+ public ChunkBuffer(ChunkPos chunkcoordintpair) {
+ super();
+ this.pos = chunkcoordintpair;
+ }
+
+ public void close() throws IOException {
+ ByteBuffer bytebuffer = ByteBuffer.wrap(this.buf, 0, this.count);
+ LinearRegionFile.this.write(this.pos, bytebuffer);
+ }
+ }
+
+ private byte[] toByteArray(InputStream in) throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ byte[] tempBuffer = new byte[4096];
+
+ int length;
+ while ((length = in.read(tempBuffer)) >= 0) {
+ out.write(tempBuffer, 0, length);
+ }
+
+ return out.toByteArray();
+ }
+
+ @Nullable
+ public synchronized DataInputStream getChunkDataInputStream(ChunkPos pos) {
+ openRegionFile();
+ openBucket(pos.x, pos.z);
+
+ if(this.bufferUncompressedSize[getChunkIndex(pos.x, pos.z)] != 0) {
+ byte[] content = new byte[bufferUncompressedSize[getChunkIndex(pos.x, pos.z)]];
+ this.decompressor.decompress(this.buffer[getChunkIndex(pos.x, pos.z)], 0, content, 0, bufferUncompressedSize[getChunkIndex(pos.x, pos.z)]);
+ return new DataInputStream(new ByteArrayInputStream(content));
+ }
+ return null;
+ }
+
+ public synchronized void clear(ChunkPos pos) {
+ openRegionFile();
+ openBucket(pos.x, pos.z);
+ int i = getChunkIndex(pos.x, pos.z);
+ this.buffer[i] = null;
+ this.bufferUncompressedSize[i] = 0;
+ this.chunkTimestamps[i] = 0;
+ markToSave();
+ }
+
+ public synchronized boolean hasChunk(ChunkPos pos) {
+ openRegionFile();
+ openBucket(pos.x, pos.z);
+ return this.bufferUncompressedSize[getChunkIndex(pos.x, pos.z)] > 0;
+ }
+
+ public synchronized void close() throws IOException {
+ openRegionFile();
+ close = true;
+ try {
+ flush();
+ } catch(IOException e) {
+ throw new IOException("Region flush IOException " + e + " " + this.regionFile);
+ }
+ }
+
+ private static int getChunkIndex(int x, int z) {
+ return (x & 31) + ((z & 31) << 5);
+ }
+
+ private static int getTimestamp() {
+ return (int) (System.currentTimeMillis() / 1000L);
+ }
+
+ public boolean recalculateHeader() {
+ return false;
+ }
+
+ public void setOversized(int x, int z, boolean something) {}
+
+ public CompoundTag getOversizedData(int x, int z) throws IOException {
+ throw new IOException("getOversizedData is a stub " + this.regionFile);
+ }
+
+ public boolean isOversized(int x, int z) {
+ return false;
+ }
+
+ public Path getPath() {
+ return this.regionFile;
+ }
+
+ private boolean[] deserializeExistenceBitmap(ByteBuffer buffer) {
+ boolean[] result = new boolean[1024];
+ for (int i = 0; i < 128; i++) {
+ byte b = buffer.get();
+ for (int j = 0; j < 8; j++) {
+ result[i * 8 + j] = ((b >> (7 - j)) & 1) == 1;
+ }
+ }
+ return result;
+ }
+
+ private void writeSerializedExistenceBitmap(DataOutputStream out, boolean[] bitmap) throws IOException {
+ for (int i = 0; i < 128; i++) {
+ byte b = 0;
+ for (int j = 0; j < 8; j++) {
+ if (bitmap[i * 8 + j]) {
+ b |= (1 << (7 - j));
+ }
+ }
+ out.writeByte(b);
+ }
+ }
+}

View File

@@ -1,93 +0,0 @@
--- /dev/null
+++ b/src/main/java/com/kiocg/ChunkHot.java
@@ -1,0 +_,90 @@
+package com.kiocg;
+
+import java.util.Arrays;
+
+public class ChunkHot {
+ // 热度统计总区间数量
+ private static final int TIMES_LENGTH = 10;
+ // 当前统计区间下标
+ private int index = -1;
+
+ // 热度统计区间
+ private final long[] times = new long[TIMES_LENGTH];
+ // 存放临时的区间数值
+ // 用于修正正在统计的当前区间热度没有计入总值的问题
+ private long temp;
+ // 所有区间的热度总值
+ private long total;
+
+ // 用于每个具体统计的计算
+ private long nanos;
+ // 当前统计是否进行中
+ private volatile boolean started = false;
+
+ /**
+ * 更新区间下标
+ */
+ public void nextTick() {
+ this.index = ++this.index % TIMES_LENGTH;
+ }
+
+ /**
+ * 开始统计一个新区间
+ */
+ public void start() {
+ started = true;
+ temp = times[this.index];
+ times[this.index] = 0L;
+ }
+
+ public boolean isStarted(){
+ return this.started;
+ }
+
+ /**
+ * 结束当前区间的统计
+ * 将统计值更新入热度总值
+ */
+ public void stop() {
+ started = false;
+ total -= temp;
+ total += times[this.index];
+ }
+
+ /**
+ * 开始一个具体统计
+ */
+ public void startTicking() {
+ if (!started) return;
+ nanos = System.nanoTime();
+ }
+
+ /**
+ * 结束一个具体统计
+ * 将统计值计入当前热度区间
+ */
+ public void stopTickingAndCount() {
+ if (!started) return;
+ // 定义一个具体统计的最大值为 1,000,000
+ // 有时候某个具体统计的计算值会在某1刻飙升可能是由于保存数据到磁盘
+ times[this.index] += Math.min(System.nanoTime() - nanos, 1000000L);
+ }
+
+ /**
+ * 清空统计 (当区块卸载时)
+ */
+ public void clear() {
+ started = false;
+ Arrays.fill(times, 0L);
+ temp = 0L;
+ total = 0L;
+ nanos = 0L;
+ }
+
+ /**
+ * @return 获取区块热度平均值
+ */
+ public long getAverage() {
+ return total / ((long) TIMES_LENGTH * 20L);
+ }
+}

View File

@@ -1,37 +0,0 @@
--- /dev/null
+++ b/src/main/java/com/logisticscraft/occlusionculling/DataProvider.java
@@ -1,0 +_,34 @@
+package com.logisticscraft.occlusionculling;
+
+import com.logisticscraft.occlusionculling.util.Vec3d;
+
+public interface DataProvider {
+
+ /**
+ * Prepares the requested chunk. Returns true if the chunk is ready, false when
+ * not loaded. Should not reload the chunk when the x and y are the same as the
+ * last request!
+ *
+ * @param chunkX
+ * @param chunkZ
+ * @return
+ */
+ boolean prepareChunk(int chunkX, int chunkZ);
+
+ /**
+ * Location is inside the chunk.
+ *
+ * @param x
+ * @param y
+ * @param z
+ * @return
+ */
+ boolean isOpaqueFullCube(int x, int y, int z);
+
+ default void cleanup() {
+ }
+
+ default void checkingPosition(Vec3d[] targetPoints, int size, Vec3d viewerPosition) {
+ }
+
+}

View File

@@ -1,518 +0,0 @@
--- /dev/null
+++ b/src/main/java/com/logisticscraft/occlusionculling/OcclusionCullingInstance.java
@@ -1,0 +_,515 @@
+package com.logisticscraft.occlusionculling;
+
+import java.util.Arrays;
+import java.util.BitSet;
+
+import com.logisticscraft.occlusionculling.cache.ArrayOcclusionCache;
+import com.logisticscraft.occlusionculling.cache.OcclusionCache;
+import com.logisticscraft.occlusionculling.util.MathUtilities;
+import com.logisticscraft.occlusionculling.util.Vec3d;
+
+public class OcclusionCullingInstance {
+
+ private static final int ON_MIN_X = 0x01;
+ private static final int ON_MAX_X = 0x02;
+ private static final int ON_MIN_Y = 0x04;
+ private static final int ON_MAX_Y = 0x08;
+ private static final int ON_MIN_Z = 0x10;
+ private static final int ON_MAX_Z = 0x20;
+
+ private final int reach;
+ private final double aabbExpansion;
+ private final DataProvider provider;
+ private final OcclusionCache cache;
+
+ // Reused allocated data structures
+ private final BitSet skipList = new BitSet(); // Grows bigger in case some mod introduces giant hitboxes
+ private final Vec3d[] targetPoints = new Vec3d[15];
+ private final Vec3d targetPos = new Vec3d(0, 0, 0);
+ private final int[] cameraPos = new int[3];
+ private final boolean[] dotselectors = new boolean[14];
+ private boolean allowRayChecks = false;
+ private final int[] lastHitBlock = new int[3];
+ private boolean allowWallClipping = false;
+
+
+ public OcclusionCullingInstance(int maxDistance, DataProvider provider) {
+ this(maxDistance, provider, new ArrayOcclusionCache(maxDistance), 0.5);
+ }
+
+ public OcclusionCullingInstance(int maxDistance, DataProvider provider, OcclusionCache cache, double aabbExpansion) {
+ this.reach = maxDistance;
+ this.provider = provider;
+ this.cache = cache;
+ this.aabbExpansion = aabbExpansion;
+ for(int i = 0; i < targetPoints.length; i++) {
+ targetPoints[i] = new Vec3d(0, 0, 0);
+ }
+ }
+
+ public boolean isAABBVisible(Vec3d aabbMin, Vec3d aabbMax, Vec3d viewerPosition) {
+ try {
+ int maxX = MathUtilities.floor(aabbMax.x
+ + aabbExpansion);
+ int maxY = MathUtilities.floor(aabbMax.y
+ + aabbExpansion);
+ int maxZ = MathUtilities.floor(aabbMax.z
+ + aabbExpansion);
+ int minX = MathUtilities.floor(aabbMin.x
+ - aabbExpansion);
+ int minY = MathUtilities.floor(aabbMin.y
+ - aabbExpansion);
+ int minZ = MathUtilities.floor(aabbMin.z
+ - aabbExpansion);
+
+ cameraPos[0] = MathUtilities.floor(viewerPosition.x);
+ cameraPos[1] = MathUtilities.floor(viewerPosition.y);
+ cameraPos[2] = MathUtilities.floor(viewerPosition.z);
+
+ Relative relX = Relative.from(minX, maxX, cameraPos[0]);
+ Relative relY = Relative.from(minY, maxY, cameraPos[1]);
+ Relative relZ = Relative.from(minZ, maxZ, cameraPos[2]);
+
+ if(relX == Relative.INSIDE && relY == Relative.INSIDE && relZ == Relative.INSIDE) {
+ return true; // We are inside of the AABB, don't cull
+ }
+
+ skipList.clear();
+
+ // Just check the cache first
+ int id = 0;
+ for (int x = minX; x <= maxX; x++) {
+ for (int y = minY; y <= maxY; y++) {
+ for (int z = minZ; z <= maxZ; z++) {
+ int cachedValue = getCacheValue(x, y, z);
+
+ if (cachedValue == 1) {
+ // non-occluding
+ return true;
+ }
+
+ if (cachedValue != 0) {
+ // was checked and it wasn't visible
+ skipList.set(id);
+ }
+ id++;
+ }
+ }
+ }
+
+ // only after the first hit wall the cache becomes valid.
+ allowRayChecks = false;
+
+ // since the cache wasn't helpfull
+ id = 0;
+ for (int x = minX; x <= maxX; x++) {
+ byte visibleOnFaceX = 0;
+ byte faceEdgeDataX = 0;
+ faceEdgeDataX |= (x == minX) ? ON_MIN_X : 0;
+ faceEdgeDataX |= (x == maxX) ? ON_MAX_X : 0;
+ visibleOnFaceX |= (x == minX && relX == Relative.POSITIVE) ? ON_MIN_X : 0;
+ visibleOnFaceX |= (x == maxX && relX == Relative.NEGATIVE) ? ON_MAX_X : 0;
+ for (int y = minY; y <= maxY; y++) {
+ byte faceEdgeDataY = faceEdgeDataX;
+ byte visibleOnFaceY = visibleOnFaceX;
+ faceEdgeDataY |= (y == minY) ? ON_MIN_Y : 0;
+ faceEdgeDataY |= (y == maxY) ? ON_MAX_Y : 0;
+ visibleOnFaceY |= (y == minY && relY == Relative.POSITIVE) ? ON_MIN_Y : 0;
+ visibleOnFaceY |= (y == maxY && relY == Relative.NEGATIVE) ? ON_MAX_Y : 0;
+ for (int z = minZ; z <= maxZ; z++) {
+ byte faceEdgeData = faceEdgeDataY;
+ byte visibleOnFace = visibleOnFaceY;
+ faceEdgeData |= (z == minZ) ? ON_MIN_Z : 0;
+ faceEdgeData |= (z == maxZ) ? ON_MAX_Z : 0;
+ visibleOnFace |= (z == minZ && relZ == Relative.POSITIVE) ? ON_MIN_Z : 0;
+ visibleOnFace |= (z == maxZ && relZ == Relative.NEGATIVE) ? ON_MAX_Z : 0;
+ if(skipList.get(id)) { // was checked and it wasn't visible
+ id++;
+ continue;
+ }
+
+ if (visibleOnFace != 0) {
+ targetPos.set(x, y, z);
+ if (isVoxelVisible(viewerPosition, targetPos, faceEdgeData, visibleOnFace)) {
+ return true;
+ }
+ }
+ id++;
+ }
+ }
+ }
+
+ return false;
+ } catch (Throwable t) {
+ // Failsafe
+ t.printStackTrace();
+ }
+ return true;
+ }
+
+ /**
+ * @param viewerPosition
+ * @param position
+ * @param faceData contains rather this Block is on the outside for a given face
+ * @param visibleOnFace contains rather a face should be concidered
+ * @return
+ */
+ private boolean isVoxelVisible(Vec3d viewerPosition, Vec3d position, byte faceData, byte visibleOnFace) {
+ int targetSize = 0;
+ Arrays.fill(dotselectors, false);
+ if((visibleOnFace & ON_MIN_X) == ON_MIN_X){
+ dotselectors[0] = true;
+ if((faceData & ~ON_MIN_X) != 0) {
+ dotselectors[1] = true;
+ dotselectors[4] = true;
+ dotselectors[5] = true;
+ }
+ dotselectors[8] = true;
+ }
+ if((visibleOnFace & ON_MIN_Y) == ON_MIN_Y){
+ dotselectors[0] = true;
+ if((faceData & ~ON_MIN_Y) != 0) {
+ dotselectors[3] = true;
+ dotselectors[4] = true;
+ dotselectors[7] = true;
+ }
+ dotselectors[9] = true;
+ }
+ if((visibleOnFace & ON_MIN_Z) == ON_MIN_Z){
+ dotselectors[0] = true;
+ if((faceData & ~ON_MIN_Z) != 0) {
+ dotselectors[1] = true;
+ dotselectors[4] = true;
+ dotselectors[5] = true;
+ }
+ dotselectors[10] = true;
+ }
+ if((visibleOnFace & ON_MAX_X) == ON_MAX_X){
+ dotselectors[4] = true;
+ if((faceData & ~ON_MAX_X) != 0) {
+ dotselectors[5] = true;
+ dotselectors[6] = true;
+ dotselectors[7] = true;
+ }
+ dotselectors[11] = true;
+ }
+ if((visibleOnFace & ON_MAX_Y) == ON_MAX_Y){
+ dotselectors[1] = true;
+ if((faceData & ~ON_MAX_Y) != 0) {
+ dotselectors[2] = true;
+ dotselectors[5] = true;
+ dotselectors[6] = true;
+ }
+ dotselectors[12] = true;
+ }
+ if((visibleOnFace & ON_MAX_Z) == ON_MAX_Z){
+ dotselectors[2] = true;
+ if((faceData & ~ON_MAX_Z) != 0) {
+ dotselectors[3] = true;
+ dotselectors[6] = true;
+ dotselectors[7] = true;
+ }
+ dotselectors[13] = true;
+ }
+
+ if (dotselectors[0])targetPoints[targetSize++].setAdd(position, 0.05, 0.05, 0.05);
+ if (dotselectors[1])targetPoints[targetSize++].setAdd(position, 0.05, 0.95, 0.05);
+ if (dotselectors[2])targetPoints[targetSize++].setAdd(position, 0.05, 0.95, 0.95);
+ if (dotselectors[3])targetPoints[targetSize++].setAdd(position, 0.05, 0.05, 0.95);
+ if (dotselectors[4])targetPoints[targetSize++].setAdd(position, 0.95, 0.05, 0.05);
+ if (dotselectors[5])targetPoints[targetSize++].setAdd(position, 0.95, 0.95, 0.05);
+ if (dotselectors[6])targetPoints[targetSize++].setAdd(position, 0.95, 0.95, 0.95);
+ if (dotselectors[7])targetPoints[targetSize++].setAdd(position, 0.95, 0.05, 0.95);
+ // middle points
+ if (dotselectors[8])targetPoints[targetSize++].setAdd(position, 0.05, 0.5, 0.5);
+ if (dotselectors[9])targetPoints[targetSize++].setAdd(position, 0.5, 0.05, 0.5);
+ if (dotselectors[10])targetPoints[targetSize++].setAdd(position, 0.5, 0.5, 0.05);
+ if (dotselectors[11])targetPoints[targetSize++].setAdd(position, 0.95, 0.5, 0.5);
+ if (dotselectors[12])targetPoints[targetSize++].setAdd(position, 0.5, 0.95, 0.5);
+ if (dotselectors[13])targetPoints[targetSize++].setAdd(position, 0.5, 0.5, 0.95);
+
+ return isVisible(viewerPosition, targetPoints, targetSize);
+ }
+
+ private boolean rayIntersection(int[] b, Vec3d rayOrigin, Vec3d rayDir) {
+ Vec3d rInv = new Vec3d(1, 1, 1).div(rayDir);
+
+ double t1 = (b[0] - rayOrigin.x) * rInv.x;
+ double t2 = (b[0] + 1 - rayOrigin.x) * rInv.x;
+ double t3 = (b[1] - rayOrigin.y) * rInv.y;
+ double t4 = (b[1] + 1 - rayOrigin.y) * rInv.y;
+ double t5 = (b[2] - rayOrigin.z) * rInv.z;
+ double t6 = (b[2] + 1 - rayOrigin.z) * rInv.z;
+
+ double tmin = Math.max(Math.max(Math.min(t1, t2), Math.min(t3, t4)), Math.min(t5, t6));
+ double tmax = Math.min(Math.min(Math.max(t1, t2), Math.max(t3, t4)), Math.max(t5, t6));
+
+ // if tmax > 0, ray (line) is intersecting AABB, but the whole AABB is behind us
+ if (tmax > 0) {
+ return false;
+ }
+
+ // if tmin > tmax, ray doesn't intersect AABB
+ if (tmin > tmax) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * returns the grid cells that intersect with this Vec3d<br>
+ * <a href=
+ * "http://playtechs.blogspot.de/2007/03/raytracing-on-grid.html">http://playtechs.blogspot.de/2007/03/raytracing-on-grid.html</a>
+ * <p>
+ * Caching assumes that all Vec3d's are inside the same block
+ */
+ private boolean isVisible(Vec3d start, Vec3d[] targets, int size) {
+ // start cell coordinate
+ int x = cameraPos[0];
+ int y = cameraPos[1];
+ int z = cameraPos[2];
+
+ for (int v = 0; v < size; v++) {
+ // ray-casting target
+ Vec3d target = targets[v];
+
+ double relativeX = start.x - target.getX();
+ double relativeY = start.y - target.getY();
+ double relativeZ = start.z - target.getZ();
+
+ if(allowRayChecks && rayIntersection(lastHitBlock, start, new Vec3d(relativeX, relativeY, relativeZ).normalize())) {
+ continue;
+ }
+
+ // horizontal and vertical cell amount spanned
+ double dimensionX = Math.abs(relativeX);
+ double dimensionY = Math.abs(relativeY);
+ double dimensionZ = Math.abs(relativeZ);
+
+ // distance between horizontal intersection points with cell border as a
+ // fraction of the total Vec3d length
+ double dimFracX = 1f / dimensionX;
+ // distance between vertical intersection points with cell border as a fraction
+ // of the total Vec3d length
+ double dimFracY = 1f / dimensionY;
+ double dimFracZ = 1f / dimensionZ;
+
+ // total amount of intersected cells
+ int intersectCount = 1;
+
+ // 1, 0 or -1
+ // determines the direction of the next cell (horizontally / vertically)
+ int x_inc, y_inc, z_inc;
+
+ // the distance to the next horizontal / vertical intersection point with a cell
+ // border as a fraction of the total Vec3d length
+ double t_next_y, t_next_x, t_next_z;
+
+ if (dimensionX == 0f) {
+ x_inc = 0;
+ t_next_x = dimFracX; // don't increment horizontally because the Vec3d is perfectly vertical
+ } else if (target.x > start.x) {
+ x_inc = 1; // target point is horizontally greater than starting point so increment every
+ // step by 1
+ intersectCount += MathUtilities.floor(target.x) - x; // increment total amount of intersecting cells
+ t_next_x = (float) ((x + 1 - start.x) * dimFracX); // calculate the next horizontal
+ // intersection
+ // point based on the position inside
+ // the first cell
+ } else {
+ x_inc = -1; // target point is horizontally smaller than starting point so reduce every step
+ // by 1
+ intersectCount += x - MathUtilities.floor(target.x); // increment total amount of intersecting cells
+ t_next_x = (float) ((start.x - x)
+ * dimFracX); // calculate the next horizontal
+ // intersection point
+ // based on the position inside
+ // the first cell
+ }
+
+ if (dimensionY == 0f) {
+ y_inc = 0;
+ t_next_y = dimFracY; // don't increment vertically because the Vec3d is perfectly horizontal
+ } else if (target.y > start.y) {
+ y_inc = 1; // target point is vertically greater than starting point so increment every
+ // step by 1
+ intersectCount += MathUtilities.floor(target.y) - y; // increment total amount of intersecting cells
+ t_next_y = (float) ((y + 1 - start.y)
+ * dimFracY); // calculate the next vertical
+ // intersection
+ // point based on the position inside
+ // the first cell
+ } else {
+ y_inc = -1; // target point is vertically smaller than starting point so reduce every step
+ // by 1
+ intersectCount += y - MathUtilities.floor(target.y); // increment total amount of intersecting cells
+ t_next_y = (float) ((start.y - y)
+ * dimFracY); // calculate the next vertical intersection
+ // point
+ // based on the position inside
+ // the first cell
+ }
+
+ if (dimensionZ == 0f) {
+ z_inc = 0;
+ t_next_z = dimFracZ; // don't increment vertically because the Vec3d is perfectly horizontal
+ } else if (target.z > start.z) {
+ z_inc = 1; // target point is vertically greater than starting point so increment every
+ // step by 1
+ intersectCount += MathUtilities.floor(target.z) - z; // increment total amount of intersecting cells
+ t_next_z = (float) ((z + 1 - start.z)
+ * dimFracZ); // calculate the next vertical
+ // intersection
+ // point based on the position inside
+ // the first cell
+ } else {
+ z_inc = -1; // target point is vertically smaller than starting point so reduce every step
+ // by 1
+ intersectCount += z - MathUtilities.floor(target.z); // increment total amount of intersecting cells
+ t_next_z = (float) ((start.z - z)
+ * dimFracZ); // calculate the next vertical intersection
+ // point
+ // based on the position inside
+ // the first cell
+ }
+
+ boolean finished = stepRay(start, x, y, z,
+ dimFracX, dimFracY, dimFracZ, intersectCount, x_inc, y_inc,
+ z_inc, t_next_y, t_next_x, t_next_z);
+ provider.cleanup();
+ if (finished) {
+ cacheResult(targets[0], true);
+ return true;
+ } else {
+ allowRayChecks = true;
+ }
+ }
+ cacheResult(targets[0], false);
+ return false;
+ }
+
+ private boolean stepRay(Vec3d start, int currentX, int currentY,
+ int currentZ, double distInX, double distInY,
+ double distInZ, int n, int x_inc, int y_inc,
+ int z_inc, double t_next_y, double t_next_x,
+ double t_next_z) {
+ allowWallClipping = true; // initially allow rays to go through walls till they are on the outside
+ // iterate through all intersecting cells (n times)
+ for (; n > 1; n--) { // n-1 times because we don't want to check the last block
+ // towards - where from
+
+
+ // get cached value, 0 means uncached (default)
+ int cVal = getCacheValue(currentX, currentY, currentZ);
+
+ if (cVal == 2 && !allowWallClipping) {
+ // block cached as occluding, stop ray
+ lastHitBlock[0] = currentX;
+ lastHitBlock[1] = currentY;
+ lastHitBlock[2] = currentZ;
+ return false;
+ }
+
+ if (cVal == 0) {
+ // save current cell
+ int chunkX = currentX >> 4;
+ int chunkZ = currentZ >> 4;
+
+ if (!provider.prepareChunk(chunkX, chunkZ)) { // Chunk not ready
+ return false;
+ }
+
+ if (provider.isOpaqueFullCube(currentX, currentY, currentZ)) {
+ if (!allowWallClipping) {
+ cache.setLastHidden();
+ lastHitBlock[0] = currentX;
+ lastHitBlock[1] = currentY;
+ lastHitBlock[2] = currentZ;
+ return false;
+ }
+ } else {
+ // outside of wall, now clipping is not allowed
+ allowWallClipping = false;
+ cache.setLastVisible();
+ }
+ }
+
+ if(cVal == 1) {
+ // outside of wall, now clipping is not allowed
+ allowWallClipping = false;
+ }
+
+
+ if (t_next_y < t_next_x && t_next_y < t_next_z) { // next cell is upwards/downwards because the distance to
+ // the next vertical
+ // intersection point is smaller than to the next horizontal intersection point
+ currentY += y_inc; // move up/down
+ t_next_y += distInY; // update next vertical intersection point
+ } else if (t_next_x < t_next_y && t_next_x < t_next_z) { // next cell is right/left
+ currentX += x_inc; // move right/left
+ t_next_x += distInX; // update next horizontal intersection point
+ } else {
+ currentZ += z_inc; // move right/left
+ t_next_z += distInZ; // update next horizontal intersection point
+ }
+
+ }
+ return true;
+ }
+
+ // -1 = invalid location, 0 = not checked yet, 1 = visible, 2 = occluding
+ private int getCacheValue(int x, int y, int z) {
+ x -= cameraPos[0];
+ y -= cameraPos[1];
+ z -= cameraPos[2];
+ if (Math.abs(x) > reach - 2 || Math.abs(y) > reach - 2
+ || Math.abs(z) > reach - 2) {
+ return -1;
+ }
+
+ // check if target is already known
+ return cache.getState(x + reach, y + reach, z + reach);
+ }
+
+
+ private void cacheResult(int x, int y, int z, boolean result) {
+ int cx = x - cameraPos[0] + reach;
+ int cy = y - cameraPos[1] + reach;
+ int cz = z - cameraPos[2] + reach;
+ if (result) {
+ cache.setVisible(cx, cy, cz);
+ } else {
+ cache.setHidden(cx, cy, cz);
+ }
+ }
+
+ private void cacheResult(Vec3d vector, boolean result) {
+ int cx = MathUtilities.floor(vector.x) - cameraPos[0] + reach;
+ int cy = MathUtilities.floor(vector.y) - cameraPos[1] + reach;
+ int cz = MathUtilities.floor(vector.z) - cameraPos[2] + reach;
+ if (result) {
+ cache.setVisible(cx, cy, cz);
+ } else {
+ cache.setHidden(cx, cy, cz);
+ }
+ }
+
+ public void resetCache() {
+ this.cache.resetCache();
+ }
+
+ private enum Relative {
+ INSIDE, POSITIVE, NEGATIVE;
+
+ public static Relative from(int min, int max, int pos) {
+ if (max > pos && min > pos) {
+ return POSITIVE;
+ } else if (min < pos && max < pos) {
+ return NEGATIVE;
+ }
+ return INSIDE;
+ }
+ }
+
+}

View File

@@ -1,60 +0,0 @@
--- /dev/null
+++ b/src/main/java/com/logisticscraft/occlusionculling/cache/ArrayOcclusionCache.java
@@ -1,0 +_,57 @@
+package com.logisticscraft.occlusionculling.cache;
+
+import java.util.Arrays;
+
+public class ArrayOcclusionCache implements OcclusionCache {
+
+ private final int reachX2;
+ private final byte[] cache;
+ private int positionKey;
+ private int entry;
+ private int offset;
+
+ public ArrayOcclusionCache(int reach) {
+ this.reachX2 = reach * 2;
+ this.cache = new byte[(reachX2 * reachX2 * reachX2) / 4];
+ }
+
+ @Override
+ public void resetCache() {
+ Arrays.fill(cache, (byte) 0);
+ }
+
+ @Override
+ public void setVisible(int x, int y, int z) {
+ positionKey = x + y * reachX2 + z * reachX2 * reachX2;
+ entry = positionKey / 4;
+ offset = (positionKey % 4) * 2;
+ cache[entry] |= 1 << offset;
+ }
+
+ @Override
+ public void setHidden(int x, int y, int z) {
+ positionKey = x + y * reachX2 + z * reachX2 * reachX2;
+ entry = positionKey / 4;
+ offset = (positionKey % 4) * 2;
+ cache[entry] |= 1 << offset + 1;
+ }
+
+ @Override
+ public int getState(int x, int y, int z) {
+ positionKey = x + y * reachX2 + z * reachX2 * reachX2;
+ entry = positionKey / 4;
+ offset = (positionKey % 4) * 2;
+ return cache[entry] >> offset & 3;
+ }
+
+ @Override
+ public void setLastVisible() {
+ cache[entry] |= 1 << offset;
+ }
+
+ @Override
+ public void setLastHidden() {
+ cache[entry] |= 1 << offset + 1;
+ }
+
+}

View File

@@ -1,20 +0,0 @@
--- /dev/null
+++ b/src/main/java/com/logisticscraft/occlusionculling/cache/OcclusionCache.java
@@ -1,0 +_,17 @@
+package com.logisticscraft.occlusionculling.cache;
+
+public interface OcclusionCache {
+
+ void resetCache();
+
+ void setVisible(int x, int y, int z);
+
+ void setHidden(int x, int y, int z);
+
+ int getState(int x, int y, int z);
+
+ void setLastHidden();
+
+ void setLastVisible();
+
+}

View File

@@ -1,28 +0,0 @@
--- /dev/null
+++ b/src/main/java/com/logisticscraft/occlusionculling/util/MathUtilities.java
@@ -1,0 +_,25 @@
+package com.logisticscraft.occlusionculling.util;
+
+/**
+ * Contains MathHelper methods
+ */
+public final class MathUtilities {
+
+ private MathUtilities() {
+ }
+
+ public static int floor(double d) {
+ int i = (int) d;
+ return d < (double) i ? i - 1 : i;
+ }
+
+ public static int fastFloor(double d) {
+ return (int) (d + 1024.0) - 1024;
+ }
+
+ public static int ceil(double d) {
+ int i = (int) d;
+ return d > (double) i ? i + 1 : i;
+ }
+
+}

View File

@@ -1,90 +0,0 @@
--- /dev/null
+++ b/src/main/java/com/logisticscraft/occlusionculling/util/Vec3d.java
@@ -1,0 +_,87 @@
+package com.logisticscraft.occlusionculling.util;
+
+public class Vec3d {
+
+ public double x;
+ public double y;
+ public double z;
+
+ public Vec3d(double x, double y, double z) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+
+ public double getX() {
+ return x;
+ }
+
+ public double getY() {
+ return y;
+ }
+
+ public double getZ() {
+ return z;
+ }
+
+ public void set(double x, double y, double z) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+
+ public void setAdd(Vec3d vec, double x, double y, double z) {
+ this.x = vec.x + x;
+ this.y = vec.y + y;
+ this.z = vec.z + z;
+ }
+
+ public Vec3d div(Vec3d rayDir) {
+ this.x /= rayDir.x;
+ this.z /= rayDir.z;
+ this.y /= rayDir.y;
+ return this;
+ }
+
+ public Vec3d normalize() {
+ double mag = Math.sqrt(x*x+y*y+z*z);
+ this.x /= mag;
+ this.y /= mag;
+ this.z /= mag;
+ return this;
+ }
+
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (!(other instanceof Vec3d)) {
+ return false;
+ }
+ Vec3d vec3d = (Vec3d) other;
+ if (Double.compare(vec3d.x, x) != 0) {
+ return false;
+ }
+ if (Double.compare(vec3d.y, y) != 0) {
+ return false;
+ }
+ return Double.compare(vec3d.z, z) == 0;
+ }
+
+ @Override
+ public int hashCode() {
+ long l = Double.doubleToLongBits(x);
+ int i = (int) (l ^ l >>> 32);
+ l = Double.doubleToLongBits(y);
+ i = 31 * i + (int) (l ^ l >>> 32);
+ l = Double.doubleToLongBits(z);
+ i = 31 * i + (int) (l ^ l >>> 32);
+ return i;
+ }
+
+ @Override
+ public String toString() {
+ return "(" + x + ", " + y + ", " + z + ")";
+ }
+
+}

View File

@@ -1,144 +0,0 @@
--- /dev/null
+++ b/src/main/java/dev/kaiijumc/kaiiju/KaiijuEntityLimits.java
@@ -1,0 +_,141 @@
+package dev.kaiijumc.kaiiju;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.logging.Level;
+
+import com.google.common.base.Throwables;
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
+import io.github.classgraph.ClassGraph;
+import io.github.classgraph.ClassInfo;
+import io.github.classgraph.ScanResult;
+import org.slf4j.Logger;
+
+import com.mojang.logging.LogUtils;
+import net.minecraft.world.entity.Entity;
+import org.bukkit.Bukkit;
+import org.bukkit.configuration.InvalidConfigurationException;
+import org.bukkit.configuration.file.YamlConfiguration;
+
+@SuppressWarnings("unused")
+public class KaiijuEntityLimits {
+ private static final Logger LOGGER = LogUtils.getLogger();
+ private static final File CONFIG_FOLDER = new File("luminol_config");
+
+ protected static final String HEADER =
+ "Per region entity limits for Kaiiju.\n"
+ + "If there are more of particular entity type in a region than limit, entity ticking will be throttled.\n"
+ + "Example: for Wither limit 100 & 300 Withers in a region -> 100 Withers tick every tick & every Wither ticks every 3 ticks.\n"
+ + "Available entities: GlowSquid, Ambient, Bat, Animal, Bee, Cat, Chicken, Cod, Cow, Dolphin, Fish, FishSchool, Fox, Golem, IronGolem, "
+ + "MushroomCow, Ocelot, Panda, Parrot, Perchable, Pig, PolarBear, PufferFish, Rabbit, Salmon, Sheep, Snowman, Squid, TropicalFish, Turtle, "
+ + "WaterAnimal, Wolf, Allay, Axolotl, Camel, Frog, Tadpole, Goat, Horse, HorseAbstract, HorseChestedAbstract, HorseDonkey, HorseMule, "
+ + "HorseSkeleton, HorseZombie, Llama, LlamaTrader, Sniffer, EnderCrystal, EnderDragon, Wither, ArmorStand, Hanging, ItemFrame, Leash, "
+ + "Painting, GlowItemFrame, FallingBlock, Item, TNTPrimed, Blaze, CaveSpider, Creeper, Drowned, Enderman, Endermite, Evoker, Ghast, "
+ + "GiantZombie, Guardian, GuardianElder, IllagerAbstract, IllagerIllusioner, IllagerWizard, MagmaCube, Monster, MonsterPatrolling, Phantom, "
+ + "PigZombie, Pillager, Ravager, Shulker, Silverfish, Skeleton, SkeletonAbstract, SkeletonStray, SkeletonWither, Slime, Spider, Strider, Vex, "
+ + "Vindicator, Witch, Zoglin, Zombie, ZombieHusk, ZombieVillager, Hoglin, Piglin, PiglinAbstract, PiglinBrute, Warden, Villager, "
+ + "VillagerTrader, Arrow, DragonFireball, Egg, EnderPearl, EnderSignal, EvokerFangs, Fireball, FireballFireball, Fireworks, FishingHook, "
+ + "LargeFireball, LlamaSpit, Potion, Projectile, ProjectileThrowable, ShulkerBullet, SmallFireball, Snowball, SpectralArrow, ThrownExpBottle, "
+ + "ThrownTrident, TippedArrow, WitherSkull, Raider, ChestBoat, Boat, MinecartAbstract, MinecartChest, MinecartCommandBlock, MinecartContainer, "
+ + "MinecartFurnace, MinecartHopper, MinecartMobSpawner, MinecartRideable, MinecartTNT\n";
+ protected static final File ENTITY_LIMITS_FILE = new File(CONFIG_FOLDER, "kaiiju_entity_limits.yml");
+ public static YamlConfiguration entityLimitsConfig;
+ public static boolean enabled = false;
+
+ protected static Map<Class<? extends Entity>, EntityLimit> entityLimits;
+
+ static final String ENTITY_PREFIX = "Entity";
+
+ public static void init() {
+ init(true);
+ }
+
+ private static void init(boolean setup) {
+ entityLimitsConfig = new YamlConfiguration();
+
+ if (ENTITY_LIMITS_FILE.exists()) {
+ try {
+ entityLimitsConfig.load(ENTITY_LIMITS_FILE);
+ } catch (InvalidConfigurationException ex) {
+ Bukkit.getLogger().log(Level.SEVERE, "Could not load kaiiju_entity_limits.yml, please correct your syntax errors", ex);
+ throw Throwables.propagate(ex);
+ } catch (IOException ignore) {}
+ } else {
+ if (setup) {
+ entityLimitsConfig.options().header(HEADER);
+ entityLimitsConfig.options().copyDefaults(true);
+ entityLimitsConfig.set("enabled", enabled);
+ entityLimitsConfig.set("Axolotl.limit", 1000);
+ entityLimitsConfig.set("Axolotl.removal", 2000);
+ try {
+ entityLimitsConfig.save(ENTITY_LIMITS_FILE);
+ } catch (IOException ex) {
+ Bukkit.getLogger().log(Level.SEVERE, "Could not save " + ENTITY_LIMITS_FILE, ex);
+ }
+ }
+ }
+
+ enabled = entityLimitsConfig.getBoolean("enabled");
+
+ entityLimits = new Object2ObjectOpenHashMap<>();
+ try (ScanResult scanResult = new ClassGraph().enableAllInfo().acceptPackages("net.minecraft.world.entity").scan()) {
+ Map<String, ClassInfo> entityClasses = new HashMap<>();
+ for (ClassInfo classInfo : scanResult.getAllClasses()) {
+ Class<?> entityClass = Class.forName(classInfo.getName());
+ if (Entity.class.isAssignableFrom(entityClass)) {
+ String entityName = extractEntityName(entityClass.getSimpleName());
+ entityClasses.put(entityName, classInfo);
+ }
+ }
+
+ for (String key : entityLimitsConfig.getKeys(false)) {
+ if (key.equals("enabled")) {
+ continue;
+ }
+
+ if (!entityClasses.containsKey(key)) {
+ LOGGER.error("Unknown entity '" + key + "' in kaiiju-entity-limits.yml, skipping");
+ continue;
+ }
+ int limit = entityLimitsConfig.getInt(key + ".limit");
+ int removal = entityLimitsConfig.getInt(key + ".removal");
+
+ if (limit < 1) {
+ LOGGER.error(key + " has a limit less than the minimum of 1, ignoring");
+ continue;
+ }
+ if (removal <= limit && removal != -1) {
+ LOGGER.error(key + " has a removal limit that is less than or equal to its limit, setting removal to limit * 10");
+ removal = limit * 10;
+ }
+
+ entityLimits.put((Class<? extends Entity>) Class.forName(entityClasses.get(key).getName()), new EntityLimit(limit, removal));
+ }
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static EntityLimit getEntityLimit(Entity entity) {
+ return entityLimits.get(entity.getClass());
+ }
+
+ private static String extractEntityName(String input) {
+ int prefixLength = ENTITY_PREFIX.length();
+
+ if (input.length() <= prefixLength || !input.startsWith(ENTITY_PREFIX)) {
+ return input;
+ } else {
+ return input.substring(prefixLength);
+ }
+ }
+
+ public record EntityLimit(int limit, int removal) {
+ @Override
+ public String toString() {
+ return "EntityLimit{limit=" + limit + ", removal=" + removal + "}";
+ }
+ }
+}

View File

@@ -1,87 +0,0 @@
--- /dev/null
+++ b/src/main/java/dev/kaiijumc/kaiiju/KaiijuEntityThrottler.java
@@ -1,0 +_,84 @@
+package dev.kaiijumc.kaiiju;
+
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
+import net.minecraft.world.entity.Entity;
+import io.papermc.paper.threadedregions.RegionizedWorldData;
+
+public class KaiijuEntityThrottler {
+ private static class TickInfo {
+ int currentTick;
+ int continueFrom;
+ int toTick;
+ int toRemove;
+ }
+
+ public static class EntityThrottlerReturn {
+ public boolean skip;
+ public boolean remove;
+ }
+
+ private final Object2ObjectOpenHashMap<KaiijuEntityLimits.EntityLimit, TickInfo> entityLimitTickInfoMap = new Object2ObjectOpenHashMap<>();
+
+ public void tickLimiterStart() {
+ for (TickInfo tickInfo : entityLimitTickInfoMap.values()) {
+ tickInfo.currentTick = 0;
+ }
+ }
+
+ public EntityThrottlerReturn tickLimiterShouldSkip(Entity entity) {
+ EntityThrottlerReturn retVal = new EntityThrottlerReturn();
+ if (entity.isRemoved()) return retVal;
+ KaiijuEntityLimits.EntityLimit entityLimit = KaiijuEntityLimits.getEntityLimit(entity);
+
+ if (entityLimit != null) {
+ TickInfo tickInfo = entityLimitTickInfoMap.computeIfAbsent(entityLimit, el -> {
+ TickInfo newTickInfo = new TickInfo();
+ newTickInfo.toTick = entityLimit.limit();
+ return newTickInfo;
+ });
+
+ tickInfo.currentTick++;
+ if (tickInfo.currentTick <= tickInfo.toRemove && entityLimit.removal() > 0) {
+ retVal.skip = false;
+ retVal.remove = true;
+ return retVal;
+ }
+
+ if (tickInfo.currentTick < tickInfo.continueFrom) {
+ retVal.skip = true;
+ return retVal;
+ }
+ if (tickInfo.currentTick - tickInfo.continueFrom < tickInfo.toTick) {
+ retVal.skip = false;
+ return retVal;
+ }
+ retVal.skip = true;
+ return retVal;
+ } else {
+ retVal.skip = false;
+ return retVal;
+ }
+ }
+
+ public void tickLimiterFinish(RegionizedWorldData regionizedWorldData) {
+ for (var entry : entityLimitTickInfoMap.entrySet()) {
+ KaiijuEntityLimits.EntityLimit entityLimit = entry.getKey();
+ TickInfo tickInfo = entry.getValue();
+
+ int additionals = 0;
+ int nextContinueFrom = tickInfo.continueFrom + tickInfo.toTick;
+ if (nextContinueFrom >= tickInfo.currentTick) {
+ additionals = entityLimit.limit() - (tickInfo.currentTick - tickInfo.continueFrom);
+ nextContinueFrom = 0;
+ }
+ tickInfo.continueFrom = nextContinueFrom;
+ tickInfo.toTick = entityLimit.limit() + additionals;
+
+ if (tickInfo.toRemove == 0 && tickInfo.currentTick > entityLimit.removal()) {
+ tickInfo.toRemove = tickInfo.currentTick - entityLimit.removal();
+ } else if (tickInfo.toRemove != 0) {
+ tickInfo.toRemove = 0;
+ }
+ }
+ }
+}

View File

@@ -1,136 +0,0 @@
--- /dev/null
+++ b/src/main/java/gg/pufferfish/pufferfish/sentry/PufferfishSentryAppender.java
@@ -1,0 +_,133 @@
+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 me.earthme.luminol.config.modules.misc.SentryConfig;
+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.getSimpleName());
+ private static final Gson GSON = new Gson();
+ private final Level logLevel;
+
+ public PufferfishSentryAppender(Level logLevel) {
+ super("PufferfishSentryAdapter", new SentryFilter(), null);
+ this.logLevel = logLevel;
+ }
+
+ @Override
+ public void append(LogEvent logEvent) {
+ if (logEvent.getLevel().isMoreSpecificThan(logLevel) && (logEvent.getThrown() != null || !SentryConfig.onlyLogThrown)) {
+ 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) {
+ return switch (level.getStandardLevel()) {
+ case TRACE, DEBUG -> SentryLevel.DEBUG;
+ case WARN -> SentryLevel.WARNING;
+ case ERROR -> SentryLevel.ERROR;
+ case FATAL -> SentryLevel.FATAL;
+ default -> SentryLevel.INFO;
+ };
+ }
+
+ private static class SentryFilter extends AbstractFilter {
+
+ @Override
+ public Result filter(Logger logger, Level level, Marker marker, String msg,
+ Object... params) {
+ return this.filter(logger.getName());
+ }
+
+ @Override
+ public Result filter(Logger logger, 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;
+ }
+ }
+}

View File

@@ -1,47 +0,0 @@
--- /dev/null
+++ b/src/main/java/gg/pufferfish/pufferfish/sentry/SentryManager.java
@@ -1,0 +_,44 @@
+package gg.pufferfish.pufferfish.sentry;
+
+import io.sentry.Sentry;
+import org.apache.logging.log4j.Level;
+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(Level logLevel) {
+ if (initialized) {
+ return;
+ }
+ if (logLevel == null) {
+ logger.error("Invalid log level, defaulting to WARN.");
+ logLevel = Level.WARN;
+ }
+ try {
+ initialized = true;
+
+ Sentry.init(options -> {
+ options.setDsn(me.earthme.luminol.config.modules.misc.SentryConfig.sentryDsn);
+ options.setMaxBreadcrumbs(100);
+ });
+
+ PufferfishSentryAppender appender = new PufferfishSentryAppender(logLevel);
+ 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;
+ }
+ }
+
+}

View File

@@ -1,30 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/api/impl/RegionStatsImpl.java
@@ -1,0 +_,27 @@
+package me.earthme.luminol.api.impl;
+
+import io.papermc.paper.threadedregions.TickRegions;
+import me.earthme.luminol.api.RegionStats;
+
+public class RegionStatsImpl implements RegionStats {
+ private final TickRegions.RegionStats internal;
+
+ public RegionStatsImpl(TickRegions.RegionStats internal) {
+ this.internal = internal;
+ }
+
+ @Override
+ public int getEntityCount() {
+ return this.internal.getEntityCount();
+ }
+
+ @Override
+ public int getPlayerCount() {
+ return this.internal.getPlayerCount();
+ }
+
+ @Override
+ public int getChunkCount() {
+ return this.internal.getChunkCount();
+ }
+}

View File

@@ -1,55 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/api/impl/ThreadedRegionImpl.java
@@ -1,0 +_,52 @@
+package me.earthme.luminol.api.impl;
+
+import io.papermc.paper.threadedregions.ThreadedRegionizer;
+import io.papermc.paper.threadedregions.TickRegions;
+import me.earthme.luminol.api.ThreadedRegion;
+import me.earthme.luminol.api.TickRegionData;
+import net.minecraft.world.level.ChunkPos;
+import org.bukkit.Location;
+import org.bukkit.World;
+
+import javax.annotation.Nullable;
+
+public class ThreadedRegionImpl implements ThreadedRegion {
+ private final ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> internal;
+
+ public ThreadedRegionImpl(ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> internal) {
+ this.internal = internal;
+ }
+
+ @Nullable
+ @Override
+ public Location getCenterChunkPos() {
+ final ChunkPos centerChunkPos = this.internal.getCenterChunk();
+
+ if (centerChunkPos == null) {
+ return null;
+ }
+
+ return new Location(this.internal.regioniser.world.getWorld(), centerChunkPos.getMiddleBlockX(), 0, centerChunkPos.getMiddleBlockZ());
+ }
+
+ @Override
+ public double getDeadSectionPercent() {
+ return this.internal.getDeadSectionPercent();
+ }
+
+ @Override
+ public TickRegionData getTickRegionData() {
+ return this.internal.getData().tickRegionDataAPI;
+ }
+
+ @Nullable
+ @Override
+ public World getWorld() {
+ return this.internal.regioniser.world.getWorld();
+ }
+
+ @Override
+ public long getId() {
+ return this.internal.id;
+ }
+}

View File

@@ -1,56 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/api/impl/ThreadedRegionizerImpl.java
@@ -1,0 +_,53 @@
+package me.earthme.luminol.api.impl;
+
+import io.papermc.paper.threadedregions.TickRegions;
+import me.earthme.luminol.api.ThreadedRegion;
+import me.earthme.luminol.api.ThreadedRegionizer;
+import net.minecraft.server.level.ServerLevel;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+public class ThreadedRegionizerImpl implements ThreadedRegionizer {
+ private final ServerLevel internal;
+
+ public ThreadedRegionizerImpl(ServerLevel internal) {
+ this.internal = internal;
+ }
+
+ @Override
+ public Collection<ThreadedRegion> getAllRegions() {
+ final List<ThreadedRegion> ret = new ArrayList<>();
+
+ this.internal.regioniser.computeForAllRegions(region -> {
+ final ThreadedRegion wrapped = new ThreadedRegionImpl(region);
+
+ ret.add(wrapped);
+ });
+
+ return ret;
+ }
+
+ @Override
+ public ThreadedRegion getAtSynchronized(int chunkX, int chunkZ) {
+ final io.papermc.paper.threadedregions.ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> got = this.internal.regioniser.getRegionAtSynchronised(chunkX, chunkZ);
+
+ if (got == null) {
+ return null;
+ }
+
+ return got.threadedRegionAPI;
+ }
+
+ @Override
+ public ThreadedRegion getAtUnSynchronized(int chunkX, int chunkZ) {
+ final io.papermc.paper.threadedregions.ThreadedRegionizer.ThreadedRegion<TickRegions.TickRegionData, TickRegions.TickRegionSectionData> got = this.internal.regioniser.getRegionAtUnsynchronised(chunkX, chunkZ);
+
+ if (got == null) {
+ return null;
+ }
+
+ return got.threadedRegionAPI;
+ }
+}

View File

@@ -1,33 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/api/impl/TickRegionDataImpl.java
@@ -1,0 +_,30 @@
+package me.earthme.luminol.api.impl;
+
+import io.papermc.paper.threadedregions.TickRegions;
+import me.earthme.luminol.api.RegionStats;
+import me.earthme.luminol.api.TickRegionData;
+import org.bukkit.World;
+
+public class TickRegionDataImpl implements TickRegionData {
+ private final TickRegions.TickRegionData internal;
+
+ public TickRegionDataImpl(TickRegions.TickRegionData internal) {
+ this.internal = internal;
+ }
+
+ @Override
+ public World getWorld() {
+ return this.internal.world.getWorld();
+ }
+
+ @Override
+ public long getCurrentTickCount() {
+ return this.internal.getCurrentTick();
+ }
+
+ @Override
+ public RegionStats getRegionStats() {
+ return this.internal.getRegionStats().regionStatsAPI;
+ }
+
+}

View File

@@ -1,124 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/commands/LuminolConfigCommand.java
@@ -1,0 +_,121 @@
+package me.earthme.luminol.commands;
+
+import me.earthme.luminol.config.LuminolConfig;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.TextColor;
+import org.bukkit.Location;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class LuminolConfigCommand extends Command {
+ public LuminolConfigCommand() {
+ super("luminolconfig");
+ this.setPermission("luminol.commands.luminolconfig");
+ this.setDescription("Manage config file");
+ this.setUsage("/luminolconfig");
+ }
+
+ public void wrongUse(CommandSender sender) {
+ sender.sendMessage(
+ Component
+ .text("Wrong use!")
+ .color(TextColor.color(255, 0, 0))
+ );
+ }
+
+ @Override
+ public @NotNull List<String> tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args, @Nullable Location location) throws IllegalArgumentException {
+ final List<String> result = new ArrayList<>();
+
+ if (args.length == 1) {
+ result.add("query");
+ result.add("set");
+ result.add("reset");
+ result.add("reload");
+ } else if (args.length == 2 && (args[0].equals("query") || args[0].equals("set") || args[0].equals("reset"))) {
+ result.addAll(LuminolConfig.completeConfigPath(args[1]));
+ }
+ return result;
+ }
+
+ @Override
+ public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) {
+ if (!this.testPermission(sender)) {
+ sender.sendMessage(Component
+ .text("No permission to execute this command!")
+ .color(TextColor.color(255, 0, 0))
+ );
+ }
+
+ if (args.length < 1) {
+ wrongUse(sender);
+ return true;
+ }
+
+ switch (args[0]) {
+ case "reload" -> {
+ LuminolConfig.reloadAsync().thenAccept(nullValue -> sender.sendMessage(
+ Component
+ .text("Reloaded config file!")
+ .color(TextColor.color(0, 255, 0))
+ ));
+ }
+ case "set" -> {
+ if (args.length == 2 || args.length > 3) {
+ wrongUse(sender);
+ return true;
+ } else if (LuminolConfig.setConfig(args[1], args[2])) {
+ LuminolConfig.reloadAsync().thenAccept(nullValue -> sender.sendMessage(
+ Component
+ .text("Set Config " + args[1] + " to " + args[2] + " successfully!")
+ .color(TextColor.color(0, 255, 0))
+ ));
+ } else {
+ sender.sendMessage(
+ Component
+ .text("Failed to set config " + args[1] + " to " + args[2] + "!")
+ .color(TextColor.color(255, 0, 0))
+ );
+ }
+ }
+ case "reset" -> {
+ if (args.length != 2) {
+ wrongUse(sender);
+ return true;
+ } else {
+ LuminolConfig.resetConfig(args[1]);
+ LuminolConfig.reloadAsync().thenAccept(nullValue -> sender.sendMessage(
+ Component
+ .text("Reset Config " + args[1] + " to " + LuminolConfig.getConfig(args[1]) + " successfully!")
+ .color(TextColor.color(0, 255, 0))
+ ));
+ }
+ }
+ case "query" -> {
+ if (args.length != 2) {
+ wrongUse(sender);
+ return true;
+ } else {
+ sender.sendMessage(
+ Component
+ .text("Config " + args[1] + " is " + LuminolConfig.getConfig(args[1]) + "!")
+ .color(TextColor.color(0, 255, 0))
+ );
+ }
+ }
+
+ default -> sender.sendMessage(
+ Component
+ .text("Unknown action!")
+ .color(TextColor.color(255, 0, 0))
+ );
+ }
+
+ return true;
+ }
+}

View File

@@ -1,50 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/commands/MembarCommand.java
@@ -1,0 +_,47 @@
+package me.earthme.luminol.commands;
+
+import me.earthme.luminol.config.modules.misc.MembarConfig;
+import me.earthme.luminol.functions.GlobalServerMemoryBar;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.TextColor;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+public class MembarCommand extends Command {
+ public MembarCommand(@NotNull String name) {
+ super(name);
+ this.setPermission("luminol.commands.membar");
+ this.setDescription("Show the memory usage through a bossbar");
+ this.setUsage("/membar");
+ }
+
+ @Override
+ public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) {
+ if (!testPermission(sender)) {
+ return true;
+ }
+
+ if (!MembarConfig.memoryBarEnabled) {
+ sender.sendMessage(Component.text("Membar was already disabled!").color(TextColor.color(255, 0, 0)));
+ return true;
+ }
+
+ if (!(sender instanceof Player player)) {
+ sender.sendMessage(Component.text("Only player can use this command!").color(TextColor.color(255, 0, 0)));
+ return true;
+ }
+
+ if (GlobalServerMemoryBar.isPlayerVisible(player)) {
+ player.sendMessage(Component.text("Disabled mem bar").color(TextColor.color(0, 255, 0)));
+ GlobalServerMemoryBar.setVisibilityForPlayer(player, false);
+ return true;
+ }
+
+ player.sendMessage(Component.text("Enabled mem bar").color(TextColor.color(0, 255, 0)));
+ GlobalServerMemoryBar.setVisibilityForPlayer(player, true);
+
+ return true;
+ }
+}

View File

@@ -1,50 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/commands/RegionBarCommand.java
@@ -1,0 +_,47 @@
+package me.earthme.luminol.commands;
+
+import me.earthme.luminol.config.modules.misc.RegionBarConfig;
+import me.earthme.luminol.functions.GlobalServerRegionBar;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.TextColor;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+public class RegionBarCommand extends Command {
+ public RegionBarCommand(@NotNull String name) {
+ super(name);
+ this.setPermission("luminol.commands.regionbar");
+ this.setDescription("Show info about your current region through a bossbar");
+ this.setUsage("/regionbar");
+ }
+
+ @Override
+ public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) {
+ if (!testPermission(sender)) {
+ return true;
+ }
+
+ if (!RegionBarConfig.regionbarEnabled) {
+ sender.sendMessage(Component.text("Regionbar was already disabled!").color(TextColor.color(255, 0, 0)));
+ return true;
+ }
+
+ if (!(sender instanceof Player player)) {
+ sender.sendMessage(Component.text("Only player can use this command!").color(TextColor.color(255, 0, 0)));
+ return true;
+ }
+
+ if (GlobalServerRegionBar.isPlayerVisible(player)) {
+ player.sendMessage(Component.text("Disabled region bar").color(TextColor.color(0, 255, 0)));
+ GlobalServerRegionBar.setVisibilityForPlayer(player, false);
+ return true;
+ }
+
+ player.sendMessage(Component.text("Enabled region bar").color(TextColor.color(0, 255, 0)));
+ GlobalServerRegionBar.setVisibilityForPlayer(player, true);
+
+ return true;
+ }
+}

View File

@@ -1,50 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/commands/TpsBarCommand.java
@@ -1,0 +_,47 @@
+package me.earthme.luminol.commands;
+
+import me.earthme.luminol.config.modules.misc.TpsBarConfig;
+import me.earthme.luminol.functions.GlobalServerTpsBar;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.TextColor;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+public class TpsBarCommand extends Command {
+ public TpsBarCommand(@NotNull String name) {
+ super(name);
+ this.setPermission("luminol.commands.tpsbar");
+ this.setDescription("Show the tps and mspt through a bossbar");
+ this.setUsage("/tpsbar");
+ }
+
+ @Override
+ public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) {
+ if (!testPermission(sender)) {
+ return true;
+ }
+
+ if (!TpsBarConfig.tpsbarEnabled) {
+ sender.sendMessage(Component.text("Tpsbar was already disabled!").color(TextColor.color(255, 0, 0)));
+ return true;
+ }
+
+ if (!(sender instanceof Player player)) {
+ sender.sendMessage(Component.text("Only player can use this command!").color(TextColor.color(255, 0, 0)));
+ return true;
+ }
+
+ if (GlobalServerTpsBar.isPlayerVisible(player)) {
+ player.sendMessage(Component.text("Disabled tps bar").color(TextColor.color(0, 255, 0)));
+ GlobalServerTpsBar.setVisibilityForPlayer(player, false);
+ return true;
+ }
+
+ player.sendMessage(Component.text("Enabled tps bar").color(TextColor.color(0, 255, 0)));
+ GlobalServerTpsBar.setVisibilityForPlayer(player, true);
+
+ return true;
+ }
+}

View File

@@ -1,10 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/DefaultTransformLogic.java
@@ -1,0 +_,7 @@
+package me.earthme.luminol.config;
+
+public class DefaultTransformLogic {
+ public Object transform(Object obj) {
+ return obj;
+ }
+}

View File

@@ -1,23 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/EnumConfigCategory.java
@@ -1,0 +_,20 @@
+package me.earthme.luminol.config;
+
+public enum EnumConfigCategory {
+ OPTIMIZATIONS("optimizations"),
+ FIXES("fixes"),
+ MISC("misc"),
+ GAMEPLAY("gameplay"),
+ EXPERIMENT("experiment"),
+ REMOVED("removed");
+
+ private final String baseKeyName;
+
+ EnumConfigCategory(String baseKeyName) {
+ this.baseKeyName = baseKeyName;
+ }
+
+ public String getBaseKeyName() {
+ return this.baseKeyName;
+ }
+}

View File

@@ -1,26 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/IConfigModule.java
@@ -1,0 +_,23 @@
+package me.earthme.luminol.config;
+
+import com.electronwill.nightconfig.core.file.CommentedFileConfig;
+import org.jetbrains.annotations.NotNull;
+
+public interface IConfigModule {
+
+ EnumConfigCategory getCategory();
+
+ String getBaseName();
+
+ default void onLoaded(CommentedFileConfig configInstance) {
+ }
+
+ default <T> T get(String keyName, T defaultValue, @NotNull CommentedFileConfig config) {
+ if (!config.contains(keyName)) {
+ config.set(keyName, defaultValue);
+ return defaultValue;
+ }
+
+ return config.get(keyName);
+ }
+}

View File

@@ -1,382 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/LuminolConfig.java
@@ -1,0 +_,379 @@
+package me.earthme.luminol.config;
+
+import com.electronwill.nightconfig.core.UnmodifiableConfig;
+import com.electronwill.nightconfig.core.file.CommentedFileConfig;
+import io.papermc.paper.threadedregions.RegionizedServer;
+import me.earthme.luminol.commands.LuminolConfigCommand;
+import me.earthme.luminol.config.flags.ConfigInfo;
+import me.earthme.luminol.config.flags.DoNotLoad;
+import me.earthme.luminol.config.flags.HotReloadUnsupported;
+import me.earthme.luminol.config.flags.TransformedConfig;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.bukkit.Bukkit;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Modifier;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+public class LuminolConfig {
+ public static final Logger logger = LogManager.getLogger();
+ private static final File baseConfigFolder = new File("luminol_config");
+ private static final File baseConfigFile = new File(baseConfigFolder, "luminol_global_config.toml");
+ private static final Set<IConfigModule> allInstanced = new HashSet<>();
+ private static final Map<String, Object> stagedConfigMap = new HashMap<>();
+ private static final Map<String, Object> defaultvalueMap = new HashMap<>();
+ public static boolean alreadyInit = false;
+ private static CommentedFileConfig configFileInstance;
+
+ public static void setupLatch() {
+ Bukkit.getCommandMap().register("luminolconfig", "luminol", new LuminolConfigCommand());
+ alreadyInit = true;
+ }
+
+ public static void reload() {
+ RegionizedServer.ensureGlobalTickThread("Reload luminol config off global region thread!");
+
+ dropAllInstanced();
+ try {
+ preLoadConfig();
+ finalizeLoadConfig();
+ } catch (Exception e) {
+ logger.error(e);
+ }
+ }
+
+ @Contract(" -> new")
+ public static @NotNull CompletableFuture<Void> reloadAsync() {
+ return CompletableFuture.runAsync(LuminolConfig::reload, task -> RegionizedServer.getInstance().addTask(() -> {
+ try {
+ task.run();
+ } catch (Exception e) {
+ logger.error(e);
+ }
+ }));
+ }
+
+ public static void dropAllInstanced() {
+ allInstanced.clear();
+ }
+
+ public static void finalizeLoadConfig() {
+ for (IConfigModule module : allInstanced) {
+ module.onLoaded(configFileInstance);
+ }
+ }
+
+ public static void preLoadConfig() throws IOException {
+ baseConfigFolder.mkdirs();
+
+ if (!baseConfigFile.exists()) {
+ baseConfigFile.createNewFile();
+ }
+
+ configFileInstance = CommentedFileConfig.of(baseConfigFile);
+
+ configFileInstance.load();
+
+ try {
+ instanceAllModule();
+ loadAllModules();
+ } catch (Exception e) {
+ logger.error("Failed to load config modules!", e);
+ throw new RuntimeException(e);
+ }
+
+ saveConfigs();
+ }
+
+ private static void loadAllModules() throws IllegalAccessException {
+ for (IConfigModule instanced : allInstanced) {
+ loadForSingle(instanced);
+ }
+ }
+
+ private static void instanceAllModule() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
+ for (Class<?> clazz : getClasses("me.earthme.luminol.config.modules")) {
+ if (IConfigModule.class.isAssignableFrom(clazz)) {
+ allInstanced.add((IConfigModule) clazz.getConstructor().newInstance());
+ }
+ }
+ }
+
+ private static void loadForSingle(@NotNull IConfigModule singleConfigModule) throws IllegalAccessException {
+ final EnumConfigCategory category = singleConfigModule.getCategory();
+
+ Field[] fields = singleConfigModule.getClass().getDeclaredFields();
+
+ for (Field field : fields) {
+ int modifiers = field.getModifiers();
+ if (Modifier.isStatic(modifiers) && !Modifier.isFinal(modifiers)) {
+ boolean skipLoad = field.getAnnotation(DoNotLoad.class) != null || (alreadyInit && field.getAnnotation(HotReloadUnsupported.class) != null);
+ ConfigInfo configInfo = field.getAnnotation(ConfigInfo.class);
+
+ if (skipLoad || configInfo == null) {
+ continue;
+ }
+
+ final String fullConfigKeyName = category.getBaseKeyName() + "." + singleConfigModule.getBaseName() + "." + configInfo.baseName();
+
+ field.setAccessible(true);
+ final Object currentValue = field.get(null);
+ if (!alreadyInit) defaultvalueMap.put(fullConfigKeyName, currentValue);
+ boolean removed = fullConfigKeyName.equals("removed.removed_config.removed");
+
+ if (!configFileInstance.contains(fullConfigKeyName) || removed) {
+ for (TransformedConfig transformedConfig : field.getAnnotationsByType(TransformedConfig.class)) {
+ final String oldConfigKeyName = String.join(".", transformedConfig.category()) + "." + transformedConfig.name();
+ Object oldValue = configFileInstance.get(oldConfigKeyName);
+ if (oldValue != null) {
+ boolean success = true;
+ if (transformedConfig.transform() && !removed) {
+ try {
+ for (Class<? extends DefaultTransformLogic> logic : transformedConfig.transformLogic()) {
+ oldValue = logic.getDeclaredConstructor().newInstance().transform(oldValue);
+ }
+ configFileInstance.add(fullConfigKeyName, oldValue);
+ } catch (Exception e) {
+ success = false;
+ logger.error("Failed to transform removed config {}!", transformedConfig.name());
+ }
+
+ if (transformedConfig.transformComments()) {
+ configFileInstance.setComment(fullConfigKeyName, configFileInstance.getComment(oldConfigKeyName));
+ }
+ }
+
+ if (success) removeConfig(oldConfigKeyName, transformedConfig.category());
+ final String comments = configInfo.comments();
+
+ if (!comments.isBlank()) configFileInstance.setComment(fullConfigKeyName, comments);
+
+ if (!removed && configFileInstance.get(fullConfigKeyName) != null) break;
+ }
+ }
+ if (removed) continue;
+ if (configFileInstance.get(fullConfigKeyName) != null) continue;
+ if (currentValue == null) {
+ throw new UnsupportedOperationException("Config " + singleConfigModule.getBaseName() + "tried to add an null default value!");
+ }
+
+ final String comments = configInfo.comments();
+
+ if (!comments.isBlank()) {
+ configFileInstance.setComment(fullConfigKeyName, comments);
+ }
+
+ configFileInstance.add(fullConfigKeyName, currentValue);
+ continue;
+ }
+
+ Object actuallyValue;
+ if (stagedConfigMap.containsKey(fullConfigKeyName)) {
+ actuallyValue = stagedConfigMap.get(fullConfigKeyName);
+ if (actuallyValue == null) actuallyValue = defaultvalueMap.get(fullConfigKeyName);
+ stagedConfigMap.remove(fullConfigKeyName);
+ } else {
+ actuallyValue = configFileInstance.get(fullConfigKeyName);
+ }
+ try {
+ actuallyValue = tryTransform(field.get(null).getClass(), actuallyValue);
+ configFileInstance.set(fullConfigKeyName, actuallyValue);
+ } catch (IllegalFormatConversionException e) {
+ resetConfig(fullConfigKeyName);
+ logger.error("Failed to transform config {}, reset to default!", fullConfigKeyName);
+ }
+ field.set(null, actuallyValue);
+ }
+ }
+ }
+
+ public static void removeConfig(String name, String[] keys) {
+ configFileInstance.remove(name);
+ Object configAtPath = configFileInstance.get(String.join(".", keys));
+ if (configAtPath instanceof UnmodifiableConfig && ((UnmodifiableConfig) configAtPath).isEmpty()) {
+ removeConfig(keys);
+ }
+ }
+
+ public static void removeConfig(String[] keys) {
+ configFileInstance.remove(String.join(".", keys));
+ Object configAtPath = configFileInstance.get(String.join(".", Arrays.copyOfRange(keys, 1, keys.length)));
+ if (configAtPath instanceof UnmodifiableConfig && ((UnmodifiableConfig) configAtPath).isEmpty()) {
+ removeConfig(Arrays.copyOfRange(keys, 1, keys.length));
+ }
+ }
+
+ public static boolean setConfig(String[] keys, Object value) {
+ return setConfig(String.join(".", keys), value);
+ }
+
+ public static boolean setConfig(String key, Object value) {
+ if (configFileInstance.contains(key) && configFileInstance.get(key) != null) {
+ stagedConfigMap.put(key, value);
+ return true;
+ }
+ return false;
+ }
+
+ private static Object tryTransform(Class<?> targetType, Object value) {
+ if (!targetType.isAssignableFrom(value.getClass())) {
+ try {
+ if (targetType == Integer.class) {
+ value = Integer.parseInt(value.toString());
+ } else if (targetType == Double.class) {
+ value = Double.parseDouble(value.toString());
+ } else if (targetType == Boolean.class) {
+ value = Boolean.parseBoolean(value.toString());
+ } else if (targetType == Long.class) {
+ value = Long.parseLong(value.toString());
+ } else if (targetType == Float.class) {
+ value = Float.parseFloat(value.toString());
+ } else if (targetType == String.class) {
+ value = value.toString();
+ }
+ } catch (Exception e) {
+ logger.error("Failed to transform value {}!", value);
+ throw new IllegalFormatConversionException((char) 0, targetType);
+ }
+ }
+ return value;
+ }
+
+ public static void saveConfigs() {
+ configFileInstance.save();
+ }
+
+ public static void resetConfig(String[] keys) {
+ resetConfig(String.join(".", keys));
+ }
+
+ public static void resetConfig(String key) {
+ stagedConfigMap.put(key, null);
+ }
+
+ public static String getConfig(String[] keys) {
+ return getConfig(String.join(".", keys));
+ }
+
+ public static String getConfig(String key) {
+ return configFileInstance.get(key).toString();
+ }
+
+ public static List<String> completeConfigPath(String partialPath) {
+ List<String> allPaths = getAllConfigPaths(partialPath);
+ List<String> result = new ArrayList<>();
+
+ for (String path : allPaths) {
+ String remaining = path.substring(partialPath.length());
+ if (remaining.isEmpty()) continue;
+
+ int dotIndex = remaining.indexOf('.');
+ String suggestion = (dotIndex == -1)
+ ? path
+ : partialPath + remaining.substring(0, dotIndex);
+
+ if (!result.contains(suggestion)) {
+ result.add(suggestion);
+ }
+ }
+ return result;
+ }
+
+ private static List<String> getAllConfigPaths(String currentPath) {
+ return defaultvalueMap.keySet().stream()
+ .filter(k -> k.startsWith(currentPath))
+ .toList();
+ }
+
+ public static @NotNull Set<Class<?>> getClasses(String pack) {
+ Set<Class<?>> classes = new LinkedHashSet<>();
+ String packageDirName = pack.replace('.', '/');
+ Enumeration<URL> dirs;
+
+ try {
+ dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
+ while (dirs.hasMoreElements()) {
+ URL url = dirs.nextElement();
+ String protocol = url.getProtocol();
+ if ("file".equals(protocol)) {
+ String filePath = URLDecoder.decode(url.getFile(), StandardCharsets.UTF_8);
+ findClassesInPackageByFile(pack, filePath, classes);
+ } else if ("jar".equals(protocol)) {
+ JarFile jar;
+ try {
+ jar = ((JarURLConnection) url.openConnection()).getJarFile();
+ Enumeration<JarEntry> entries = jar.entries();
+ findClassesInPackageByJar(pack, entries, packageDirName, classes);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ return classes;
+ }
+
+ private static void findClassesInPackageByFile(String packageName, String packagePath, Set<Class<?>> classes) {
+ File dir = new File(packagePath);
+
+ if (!dir.exists() || !dir.isDirectory()) {
+ return;
+ }
+
+ File[] dirfiles = dir.listFiles((file) -> file.isDirectory() || file.getName().endsWith(".class"));
+ if (dirfiles != null) {
+ for (File file : dirfiles) {
+ if (file.isDirectory()) {
+ findClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), classes);
+ } else {
+ String className = file.getName().substring(0, file.getName().length() - 6);
+ try {
+ classes.add(Class.forName(packageName + '.' + className));
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+ }
+
+ private static void findClassesInPackageByJar(String packageName, Enumeration<JarEntry> entries, String packageDirName, Set<Class<?>> classes) {
+ while (entries.hasMoreElements()) {
+ JarEntry entry = entries.nextElement();
+ String name = entry.getName();
+ if (name.charAt(0) == '/') {
+ name = name.substring(1);
+ }
+ if (name.startsWith(packageDirName)) {
+ int idx = name.lastIndexOf('/');
+ if (idx != -1) {
+ packageName = name.substring(0, idx).replace('/', '.');
+ }
+ if (name.endsWith(".class") && !entry.isDirectory()) {
+ String className = name.substring(packageName.length() + 1, name.length() - 6);
+ try {
+ classes.add(Class.forName(packageName + '.' + className));
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+ }
+}

View File

@@ -1,14 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/flags/ConfigInfo.java
@@ -1,0 +_,11 @@
+package me.earthme.luminol.config.flags;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ConfigInfo {
+ String baseName();
+
+ String comments() default "";
+}

View File

@@ -1,11 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/flags/DoNotLoad.java
@@ -1,0 +_,8 @@
+package me.earthme.luminol.config.flags;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface DoNotLoad {
+}

View File

@@ -1,11 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/flags/HotReloadUnsupported.java
@@ -1,0 +_,8 @@
+package me.earthme.luminol.config.flags;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface HotReloadUnsupported {
+}

View File

@@ -1,29 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/flags/TransformedConfig.java
@@ -1,0 +_,26 @@
+package me.earthme.luminol.config.flags;
+
+import me.earthme.luminol.config.DefaultTransformLogic;
+
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Repeatable(TransformedConfig.List.class)
+public @interface TransformedConfig {
+ String name();
+
+ String[] category();
+
+ boolean transform() default true;
+
+ boolean transformComments() default true;
+
+ Class<? extends DefaultTransformLogic>[] transformLogic() default {DefaultTransformLogic.class};
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface List {
+ TransformedConfig[] value();
+ }
+}

View File

@@ -1,23 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/modules/experiment/CommandDataConfig.java
@@ -1,0 +_,20 @@
+package me.earthme.luminol.config.modules.experiment;
+
+import me.earthme.luminol.config.EnumConfigCategory;
+import me.earthme.luminol.config.IConfigModule;
+import me.earthme.luminol.config.flags.ConfigInfo;
+
+public class CommandDataConfig implements IConfigModule {
+ @ConfigInfo(baseName = "enable")
+ public static boolean enabled = false;
+
+ @Override
+ public EnumConfigCategory getCategory() {
+ return EnumConfigCategory.EXPERIMENT;
+ }
+
+ @Override
+ public String getBaseName() {
+ return "force_the_data_command_to_be_enabled";
+ }
+}

View File

@@ -1,23 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/modules/experiment/DisableAsyncCatcherConfig.java
@@ -1,0 +_,20 @@
+package me.earthme.luminol.config.modules.experiment;
+
+import me.earthme.luminol.config.EnumConfigCategory;
+import me.earthme.luminol.config.IConfigModule;
+import me.earthme.luminol.config.flags.ConfigInfo;
+
+public class DisableAsyncCatcherConfig implements IConfigModule {
+ @ConfigInfo(baseName = "enabled")
+ public static boolean enabled = false;
+
+ @Override
+ public EnumConfigCategory getCategory() {
+ return EnumConfigCategory.EXPERIMENT;
+ }
+
+ @Override
+ public String getBaseName() {
+ return "disable_async_catchers";
+ }
+}

View File

@@ -1,23 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/modules/experiment/DisableEntityCatchConfig.java
@@ -1,0 +_,20 @@
+package me.earthme.luminol.config.modules.experiment;
+
+import me.earthme.luminol.config.EnumConfigCategory;
+import me.earthme.luminol.config.IConfigModule;
+import me.earthme.luminol.config.flags.ConfigInfo;
+
+public class DisableEntityCatchConfig implements IConfigModule {
+ @ConfigInfo(baseName = "enabled")
+ public static boolean enabled = false;
+
+ @Override
+ public EnumConfigCategory getCategory() {
+ return EnumConfigCategory.EXPERIMENT;
+ }
+
+ @Override
+ public String getBaseName() {
+ return "disable_entity_exception_catchers";
+ }
+}

View File

@@ -1,31 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/modules/fixes/FoliaEntityMovingFixConfig.java
@@ -1,0 +_,28 @@
+package me.earthme.luminol.config.modules.fixes;
+
+import me.earthme.luminol.config.EnumConfigCategory;
+import me.earthme.luminol.config.IConfigModule;
+import me.earthme.luminol.config.flags.ConfigInfo;
+
+public class FoliaEntityMovingFixConfig implements IConfigModule {
+ @ConfigInfo(baseName = "enabled", comments =
+ """
+ A simple fix of an issue on folia\s
+ (Sometimes the entity would\s
+ have a large moment that cross the\s
+ different tick regions, and it would\s
+ make the server crashed) but sometimes it might doesn't work""")
+ public static boolean enabled = false;
+ @ConfigInfo(baseName = "warn_on_detected")
+ public static boolean warnOnDetected = true;
+
+ @Override
+ public EnumConfigCategory getCategory() {
+ return EnumConfigCategory.FIXES;
+ }
+
+ @Override
+ public String getBaseName() {
+ return "folia.fix_high_velocity_issue";
+ }
+}

View File

@@ -1,28 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/modules/fixes/FoliaPOIAccessOffRegionFixConfig.java
@@ -1,0 +_,25 @@
+package me.earthme.luminol.config.modules.fixes;
+
+import me.earthme.luminol.config.EnumConfigCategory;
+import me.earthme.luminol.config.IConfigModule;
+import me.earthme.luminol.config.flags.ConfigInfo;
+
+public class FoliaPOIAccessOffRegionFixConfig implements IConfigModule {
+ @ConfigInfo(baseName = "enabled", comments =
+ """
+ The POIManager of folia has something which has not been patched\s
+ for regionized ticking and these would trigger the async catcher\s
+ and make the server crash.If you would like to prevent it and didn't\s
+ mind the side effect(currently unknown), you can enable this""")
+ public static boolean enabled = false;
+
+ @Override
+ public EnumConfigCategory getCategory() {
+ return EnumConfigCategory.FIXES;
+ }
+
+ @Override
+ public String getBaseName() {
+ return "folia.fix_poi_access_off_region";
+ }
+}

View File

@@ -1,25 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/modules/fixes/ForceCleanupEntityBrainMemoryConfig.java
@@ -1,0 +_,22 @@
+package me.earthme.luminol.config.modules.fixes;
+
+import me.earthme.luminol.config.EnumConfigCategory;
+import me.earthme.luminol.config.IConfigModule;
+import me.earthme.luminol.config.flags.ConfigInfo;
+
+public class ForceCleanupEntityBrainMemoryConfig implements IConfigModule {
+ @ConfigInfo(baseName = "enabled_for_entity", comments = "When enabled, the entity's brain will clean the memory which is typed of entity and not belong to current tickregion")
+ public static boolean enabledForEntity = false;
+ @ConfigInfo(baseName = "enabled_for_block_pos", comments = "When enabled, the entity's brain will clean the memory which is typed of block_pos and not belong to current tickregion")
+ public static boolean enabledForBlockPos = false;
+
+ @Override
+ public EnumConfigCategory getCategory() {
+ return EnumConfigCategory.EXPERIMENT;
+ }
+
+ @Override
+ public String getBaseName() {
+ return "force_cleanup_drop_non_owned_entity_memory_module";
+ }
+}

View File

@@ -1,25 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/modules/fixes/UnsafeTeleportationConfig.java
@@ -1,0 +_,22 @@
+package me.earthme.luminol.config.modules.fixes;
+
+import me.earthme.luminol.config.EnumConfigCategory;
+import me.earthme.luminol.config.IConfigModule;
+import me.earthme.luminol.config.flags.ConfigInfo;
+
+public class UnsafeTeleportationConfig implements IConfigModule {
+ @ConfigInfo(baseName = "enabled", comments = "Allow non player entities enter end portals if enabled.\n" +
+ "If you want to use sand duping,please turn on this.\n" +
+ "Warning: This would cause some unsafe issues, you could learn more on : https://github.com/PaperMC/Folia/issues/297")
+ public static boolean enabled = false;
+
+ @Override
+ public EnumConfigCategory getCategory() {
+ return EnumConfigCategory.FIXES;
+ }
+
+ @Override
+ public String getBaseName() {
+ return "allow_unsafe_teleportation";
+ }
+}

View File

@@ -1,23 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/modules/fixes/VanillaRandomSourceConfig.java
@@ -1,0 +_,20 @@
+package me.earthme.luminol.config.modules.fixes;
+
+import me.earthme.luminol.config.EnumConfigCategory;
+import me.earthme.luminol.config.IConfigModule;
+import me.earthme.luminol.config.flags.ConfigInfo;
+
+public class VanillaRandomSourceConfig implements IConfigModule {
+ @ConfigInfo(baseName = "enable_for_player_entity", comments = "Related with RNG cracks")
+ public static boolean useLegacyRandomSourceForPlayers = false;
+
+ @Override
+ public EnumConfigCategory getCategory() {
+ return EnumConfigCategory.FIXES;
+ }
+
+ @Override
+ public String getBaseName() {
+ return "use_vanilla_random_source";
+ }
+}

View File

@@ -1,28 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/CollisionBehaviorConfig.java
@@ -1,0 +_,25 @@
+package me.earthme.luminol.config.modules.misc;
+
+import me.earthme.luminol.config.EnumConfigCategory;
+import me.earthme.luminol.config.IConfigModule;
+import me.earthme.luminol.config.flags.ConfigInfo;
+
+public class CollisionBehaviorConfig implements IConfigModule {
+ @ConfigInfo(baseName = "mode", comments =
+ """
+ Available Value:
+ VANILLA
+ BLOCK_SHAPE_VANILLA
+ PAPER""")
+ public static String behaviorMode = "BLOCK_SHAPE_VANILLA";
+
+ @Override
+ public EnumConfigCategory getCategory() {
+ return EnumConfigCategory.MISC;
+ }
+
+ @Override
+ public String getBaseName() {
+ return "collision_behavior";
+ }
+}

View File

@@ -1,25 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/DisableHeightmapWarnConfig.java
@@ -1,0 +_,22 @@
+package me.earthme.luminol.config.modules.misc;
+
+import me.earthme.luminol.config.EnumConfigCategory;
+import me.earthme.luminol.config.IConfigModule;
+import me.earthme.luminol.config.flags.ConfigInfo;
+
+public class DisableHeightmapWarnConfig implements IConfigModule {
+ @ConfigInfo(baseName = "enabled", comments =
+ """
+ Disable heightmap-check's warning""")
+ public static boolean enabled = false;
+
+ @Override
+ public EnumConfigCategory getCategory() {
+ return EnumConfigCategory.MISC;
+ }
+
+ @Override
+ public String getBaseName() {
+ return "heightmap_warn_disable";
+ }
+}

View File

@@ -1,23 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/DisableMovedWronglyThreshold.java
@@ -1,0 +_,20 @@
+package me.earthme.luminol.config.modules.misc;
+
+import me.earthme.luminol.config.EnumConfigCategory;
+import me.earthme.luminol.config.IConfigModule;
+import me.earthme.luminol.config.flags.ConfigInfo;
+
+public class DisableMovedWronglyThreshold implements IConfigModule {
+ @ConfigInfo(baseName = "enabled")
+ public static boolean enabled = false;
+
+ @Override
+ public EnumConfigCategory getCategory() {
+ return EnumConfigCategory.MISC;
+ }
+
+ @Override
+ public String getBaseName() {
+ return "disable_moved_wrongly_threshold";
+ }
+}

View File

@@ -1,23 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/FoliaWatchogConfig.java
@@ -1,0 +_,20 @@
+package me.earthme.luminol.config.modules.misc;
+
+import me.earthme.luminol.config.EnumConfigCategory;
+import me.earthme.luminol.config.IConfigModule;
+import me.earthme.luminol.config.flags.ConfigInfo;
+
+public class FoliaWatchogConfig implements IConfigModule {
+ @ConfigInfo(baseName = "tick_region_time_out_ms")
+ public static int tickRegionTimeOutMs = 5000;
+
+ @Override
+ public EnumConfigCategory getCategory() {
+ return EnumConfigCategory.MISC;
+ }
+
+ @Override
+ public String getBaseName() {
+ return "folia_watchdog";
+ }
+}

View File

@@ -1,23 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/InorderChatConfig.java
@@ -1,0 +_,20 @@
+package me.earthme.luminol.config.modules.misc;
+
+import me.earthme.luminol.config.EnumConfigCategory;
+import me.earthme.luminol.config.IConfigModule;
+import me.earthme.luminol.config.flags.ConfigInfo;
+
+public class InorderChatConfig implements IConfigModule {
+ @ConfigInfo(baseName = "enabled")
+ public static boolean enabled = true;
+
+ @Override
+ public EnumConfigCategory getCategory() {
+ return EnumConfigCategory.MISC;
+ }
+
+ @Override
+ public String getBaseName() {
+ return "mojang_out_of_order_chat_check";
+ }
+}

View File

@@ -1,26 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/KaiijuEntityLimiterConfig.java
@@ -1,0 +_,23 @@
+package me.earthme.luminol.config.modules.misc;
+
+import com.electronwill.nightconfig.core.file.CommentedFileConfig;
+import dev.kaiijumc.kaiiju.KaiijuEntityLimits;
+import me.earthme.luminol.config.EnumConfigCategory;
+import me.earthme.luminol.config.IConfigModule;
+
+public class KaiijuEntityLimiterConfig implements IConfigModule {
+ @Override
+ public EnumConfigCategory getCategory() {
+ return EnumConfigCategory.MISC;
+ }
+
+ @Override
+ public String getBaseName() {
+ return "kaiiju_entity_limiter";
+ }
+
+ @Override
+ public void onLoaded(CommentedFileConfig configInstance) {
+ KaiijuEntityLimits.init();
+ }
+}

View File

@@ -1,53 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/MembarConfig.java
@@ -1,0 +_,50 @@
+package me.earthme.luminol.config.modules.misc;
+
+import com.electronwill.nightconfig.core.file.CommentedFileConfig;
+import me.earthme.luminol.commands.MembarCommand;
+import me.earthme.luminol.config.EnumConfigCategory;
+import me.earthme.luminol.config.IConfigModule;
+import me.earthme.luminol.config.flags.ConfigInfo;
+import me.earthme.luminol.config.flags.DoNotLoad;
+import me.earthme.luminol.functions.GlobalServerMemoryBar;
+import org.bukkit.Bukkit;
+
+import java.util.List;
+
+public class MembarConfig implements IConfigModule {
+ @ConfigInfo(baseName = "enabled")
+ public static boolean memoryBarEnabled = false;
+ @ConfigInfo(baseName = "format")
+ public static String memBarFormat = "<gray>Memory usage <yellow>:</yellow> <used>MB<yellow>/</yellow><available>MB";
+ @ConfigInfo(baseName = "memory_color_list")
+ public static List<String> memColors = List.of("GREEN", "YELLOW", "RED", "PURPLE");
+ @ConfigInfo(baseName = "update_interval_ticks")
+ public static int updateInterval = 15;
+
+ @DoNotLoad
+ private static boolean inited = false;
+
+ @Override
+ public EnumConfigCategory getCategory() {
+ return EnumConfigCategory.MISC;
+ }
+
+ @Override
+ public String getBaseName() {
+ return "membar";
+ }
+
+ @Override
+ public void onLoaded(CommentedFileConfig configInstance) {
+ if (memoryBarEnabled) {
+ GlobalServerMemoryBar.init();
+ } else {
+ GlobalServerMemoryBar.cancelBarUpdateTask();
+ }
+
+ if (!inited) {
+ Bukkit.getCommandMap().register("membar", "luminol", new MembarCommand("membar"));
+ inited = true;
+ }
+ }
+}

View File

@@ -1,23 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/OfflineModeWarningConfig.java
@@ -1,0 +_,20 @@
+package me.earthme.luminol.config.modules.misc;
+
+import me.earthme.luminol.config.EnumConfigCategory;
+import me.earthme.luminol.config.IConfigModule;
+import me.earthme.luminol.config.flags.ConfigInfo;
+
+public class OfflineModeWarningConfig implements IConfigModule {
+ @ConfigInfo(baseName = "enabled")
+ public static boolean enabled = true;
+
+ @Override
+ public EnumConfigCategory getCategory() {
+ return EnumConfigCategory.MISC;
+ }
+
+ @Override
+ public String getBaseName() {
+ return "warn_on_offline_mode";
+ }
+}

View File

@@ -1,24 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/PublickeyVerifyConfig.java
@@ -1,0 +_,21 @@
+package me.earthme.luminol.config.modules.misc;
+
+import me.earthme.luminol.config.EnumConfigCategory;
+import me.earthme.luminol.config.IConfigModule;
+import me.earthme.luminol.config.flags.ConfigInfo;
+
+public class PublickeyVerifyConfig implements IConfigModule {
+
+ @ConfigInfo(baseName = "enabled")
+ public static boolean enabled = false;
+
+ @Override
+ public EnumConfigCategory getCategory() {
+ return EnumConfigCategory.MISC;
+ }
+
+ @Override
+ public String getBaseName() {
+ return "verify_publickey_only_in_online_mode";
+ }
+}

View File

@@ -1,53 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/RegionBarConfig.java
@@ -1,0 +_,50 @@
+package me.earthme.luminol.config.modules.misc;
+
+import com.electronwill.nightconfig.core.file.CommentedFileConfig;
+import me.earthme.luminol.commands.RegionBarCommand;
+import me.earthme.luminol.config.EnumConfigCategory;
+import me.earthme.luminol.config.IConfigModule;
+import me.earthme.luminol.config.flags.ConfigInfo;
+import me.earthme.luminol.config.flags.DoNotLoad;
+import me.earthme.luminol.functions.GlobalServerRegionBar;
+import org.bukkit.Bukkit;
+
+import java.util.List;
+
+public class RegionBarConfig implements IConfigModule {
+ @ConfigInfo(baseName = "enabled")
+ public static boolean regionbarEnabled = false;
+ @ConfigInfo(baseName = "format")
+ public static String regionBarFormat = "<gray>Util<yellow>:</yellow> <util> Chunks<yellow>:</yellow> <green><chunks></green> Players<yellow>:</yellow> <green><players></green> Entities<yellow>:</yellow> <green><entities></green>";
+ @ConfigInfo(baseName = "util_color_list")
+ public static List<String> utilColors = List.of("GREEN", "YELLOW", "RED", "PURPLE");
+ @ConfigInfo(baseName = "update_interval_ticks")
+ public static int updateInterval = 15;
+
+ @DoNotLoad
+ private static boolean inited = false;
+
+ @Override
+ public EnumConfigCategory getCategory() {
+ return EnumConfigCategory.MISC;
+ }
+
+ @Override
+ public String getBaseName() {
+ return "regionbar";
+ }
+
+ @Override
+ public void onLoaded(CommentedFileConfig configInstance) {
+ if (regionbarEnabled) {
+ GlobalServerRegionBar.init();
+ } else {
+ GlobalServerRegionBar.cancelBarUpdateTask();
+ }
+
+ if (!inited) {
+ Bukkit.getCommandMap().register("regionbar", "luminol", new RegionBarCommand("regionbar"));
+ inited = true;
+ }
+ }
+}

View File

@@ -1,66 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/RegionFormatConfig.java
@@ -1,0 +_,63 @@
+package me.earthme.luminol.config.modules.misc;
+
+import abomination.LinearRegionFile;
+import com.electronwill.nightconfig.core.file.CommentedFileConfig;
+import me.earthme.luminol.config.EnumConfigCategory;
+import me.earthme.luminol.config.IConfigModule;
+import me.earthme.luminol.config.flags.ConfigInfo;
+import me.earthme.luminol.config.flags.DoNotLoad;
+import me.earthme.luminol.config.flags.HotReloadUnsupported;
+import me.earthme.luminol.utils.EnumRegionFormat;
+import net.minecraft.server.MinecraftServer;
+
+public class RegionFormatConfig implements IConfigModule {
+ @HotReloadUnsupported
+ @ConfigInfo(baseName = "format")
+ public static String format = "MCA";
+ @HotReloadUnsupported
+ @ConfigInfo(baseName = "linear_compression_level")
+ public static int linearCompressionLevel = 1;
+ @HotReloadUnsupported
+ @ConfigInfo(baseName = "linear_io_thread_count")
+ public static int linearIoThreadCount = 6;
+ @HotReloadUnsupported
+ @ConfigInfo(baseName = "linear_io_flush_delay_ms")
+ public static int linearIoFlushDelayMs = 100;
+ @HotReloadUnsupported
+ @ConfigInfo(baseName = "linear_use_virtual_thread")
+ public static boolean linearUseVirtualThread = true;
+
+ @DoNotLoad
+ public static EnumRegionFormat regionFormat;
+
+ @Override
+ public EnumConfigCategory getCategory() {
+ return EnumConfigCategory.MISC;
+ }
+
+ @Override
+ public String getBaseName() {
+ return "region_format";
+ }
+
+ @Override
+ public void onLoaded(CommentedFileConfig configInstance) {
+ regionFormat = EnumRegionFormat.fromString(format.toUpperCase());
+
+ if (regionFormat == null) {
+ throw new RuntimeException("Invalid region format: " + format);
+ }
+
+ if (regionFormat == EnumRegionFormat.LINEAR_V2) {
+ if (RegionFormatConfig.linearCompressionLevel > 23 || RegionFormatConfig.linearCompressionLevel < 1) {
+ MinecraftServer.LOGGER.error("Linear region compression level should be between 1 and 22 in config: {}", RegionFormatConfig.linearCompressionLevel);
+ MinecraftServer.LOGGER.error("Falling back to compression level 1.");
+ RegionFormatConfig.linearCompressionLevel = 1;
+ }
+
+ LinearRegionFile.SAVE_DELAY_MS = linearIoFlushDelayMs;
+ LinearRegionFile.SAVE_THREAD_MAX_COUNT = linearIoThreadCount;
+ LinearRegionFile.USE_VIRTUAL_THREAD = linearUseVirtualThread;
+ }
+ }
+}

View File

@@ -1,25 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/SecureSeedConfig.java
@@ -1,0 +_,22 @@
+package me.earthme.luminol.config.modules.misc;
+
+import me.earthme.luminol.config.EnumConfigCategory;
+import me.earthme.luminol.config.IConfigModule;
+import me.earthme.luminol.config.flags.ConfigInfo;
+
+public class SecureSeedConfig implements IConfigModule {
+ @ConfigInfo(baseName = "enabled", comments = """
+ Once you enable secure seed, all ores and structures are generated with 1024-bit seed
+ instead of using 64-bit seed in vanilla, made seed cracker become impossible.""")
+ public static boolean enabled = false;
+
+ @Override
+ public EnumConfigCategory getCategory() {
+ return EnumConfigCategory.MISC;
+ }
+
+ @Override
+ public String getBaseName() {
+ return "secure_seed";
+ }
+}

View File

@@ -1,50 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/SentryConfig.java
@@ -1,0 +_,47 @@
+package me.earthme.luminol.config.modules.misc;
+
+import com.electronwill.nightconfig.core.file.CommentedFileConfig;
+import me.earthme.luminol.config.EnumConfigCategory;
+import me.earthme.luminol.config.IConfigModule;
+import me.earthme.luminol.config.flags.ConfigInfo;
+import org.apache.logging.log4j.Level;
+
+public class SentryConfig implements IConfigModule {
+
+ @ConfigInfo(baseName = "dsn", comments =
+ " Sentry DSN for improved error logging, leave blank to disable,\n" +
+ " Obtain from https://sentry.io/")
+ public static String sentryDsn = "";
+
+ @ConfigInfo(baseName = "log_level", comments = " Logs with a level higher than or equal to this level will be recorded.")
+ public static String logLevel = "WARN";
+
+ @ConfigInfo(baseName = "only_log_thrown", comments = " Only log with a Throwable will be recorded after enabling this.")
+ public static boolean onlyLogThrown = true;
+
+ @Override
+ public EnumConfigCategory getCategory() {
+ return EnumConfigCategory.MISC;
+ }
+
+ @Override
+ public String getBaseName() {
+ return "sentry";
+ }
+
+ @Override
+ public void onLoaded(CommentedFileConfig configInstance) {
+ String sentryEnvironment = System.getenv("SENTRY_DSN");
+
+ sentryDsn = sentryEnvironment != null && !sentryEnvironment.isBlank()
+ ? sentryEnvironment
+ : configInstance.getOrElse("sentry.dsn", sentryDsn);
+
+ logLevel = configInstance.getOrElse("sentry.log-level", logLevel);
+ onlyLogThrown = configInstance.getOrElse("sentry.only-log-thrown", onlyLogThrown);
+
+ if (sentryDsn != null && !sentryDsn.isBlank()) {
+ gg.pufferfish.pufferfish.sentry.SentryManager.init(Level.getLevel(logLevel));
+ }
+ }
+}

View File

@@ -1,26 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/ServerModNameConfig.java
@@ -1,0 +_,23 @@
+package me.earthme.luminol.config.modules.misc;
+
+import me.earthme.luminol.config.EnumConfigCategory;
+import me.earthme.luminol.config.IConfigModule;
+import me.earthme.luminol.config.flags.ConfigInfo;
+
+public class ServerModNameConfig implements IConfigModule {
+ @ConfigInfo(baseName = "name")
+ public static String serverModName = "Luminol";
+
+ @ConfigInfo(baseName = "vanilla_spoof")
+ public static boolean fakeVanilla = false;
+
+ @Override
+ public EnumConfigCategory getCategory() {
+ return EnumConfigCategory.MISC;
+ }
+
+ @Override
+ public String getBaseName() {
+ return "server_mod_name";
+ }
+}

View File

@@ -1,57 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/TpsBarConfig.java
@@ -1,0 +_,54 @@
+package me.earthme.luminol.config.modules.misc;
+
+import com.electronwill.nightconfig.core.file.CommentedFileConfig;
+import me.earthme.luminol.commands.TpsBarCommand;
+import me.earthme.luminol.config.EnumConfigCategory;
+import me.earthme.luminol.config.IConfigModule;
+import me.earthme.luminol.config.flags.ConfigInfo;
+import me.earthme.luminol.config.flags.DoNotLoad;
+import me.earthme.luminol.functions.GlobalServerTpsBar;
+import org.bukkit.Bukkit;
+
+import java.util.List;
+
+public class TpsBarConfig implements IConfigModule {
+ @ConfigInfo(baseName = "enabled")
+ public static boolean tpsbarEnabled = false;
+ @ConfigInfo(baseName = "format")
+ public static String tpsBarFormat = "<gray>TPS<yellow>:</yellow> <tps> MSPT<yellow>:</yellow> <mspt> Ping<yellow>:</yellow> <ping>ms ChunkHot<yellow>:</yellow> <chunkhot>";
+ @ConfigInfo(baseName = "tps_color_list")
+ public static List<String> tpsColors = List.of("GREEN", "YELLOW", "RED", "PURPLE");
+ @ConfigInfo(baseName = "ping_color_list")
+ public static List<String> pingColors = List.of("GREEN", "YELLOW", "RED", "PURPLE");
+ @ConfigInfo(baseName = "chunkhot_color_list")
+ public static List<String> chunkHotColors = List.of("GREEN", "YELLOW", "RED", "PURPLE");
+ @ConfigInfo(baseName = "update_interval_ticks")
+ public static int updateInterval = 15;
+
+ @DoNotLoad
+ private static boolean inited = false;
+
+ @Override
+ public EnumConfigCategory getCategory() {
+ return EnumConfigCategory.MISC;
+ }
+
+ @Override
+ public String getBaseName() {
+ return "tpsbar";
+ }
+
+ @Override
+ public void onLoaded(CommentedFileConfig configInstance) {
+ if (tpsbarEnabled) {
+ GlobalServerTpsBar.init();
+ } else {
+ GlobalServerTpsBar.cancelBarUpdateTask();
+ }
+
+ if (!inited) {
+ Bukkit.getCommandMap().register("tpsbar", "luminol", new TpsBarCommand("tpsbar"));
+ inited = true;
+ }
+ }
+}

View File

@@ -1,32 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/TripwireBehaviorConfig.java
@@ -1,0 +_,29 @@
+package me.earthme.luminol.config.modules.misc;
+
+import me.earthme.luminol.config.EnumConfigCategory;
+import me.earthme.luminol.config.IConfigModule;
+import me.earthme.luminol.config.flags.ConfigInfo;
+import me.earthme.luminol.config.flags.TransformedConfig;
+
+public class TripwireBehaviorConfig implements IConfigModule {
+ @ConfigInfo(baseName = "enabled")
+ public static boolean enabled = false;
+ @TransformedConfig(name = "behavior-mode", category = {"misc", "tripwire_dupe"})
+ @ConfigInfo(baseName = "behavior_mode", comments =
+ """
+ Available Value:
+ VANILLA20
+ VANILLA21
+ MIXED""")
+ public static String behaviorMode = "VANILLA21";
+
+ @Override
+ public EnumConfigCategory getCategory() {
+ return EnumConfigCategory.MISC;
+ }
+
+ @Override
+ public String getBaseName() {
+ return "tripwire_dupe";
+ }
+}

View File

@@ -1,23 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/modules/misc/UsernameCheckConfig.java
@@ -1,0 +_,20 @@
+package me.earthme.luminol.config.modules.misc;
+
+import me.earthme.luminol.config.EnumConfigCategory;
+import me.earthme.luminol.config.IConfigModule;
+import me.earthme.luminol.config.flags.ConfigInfo;
+
+public class UsernameCheckConfig implements IConfigModule {
+ @ConfigInfo(baseName = "enabled")
+ public static boolean enabled = true;
+
+ @Override
+ public EnumConfigCategory getCategory() {
+ return EnumConfigCategory.MISC;
+ }
+
+ @Override
+ public String getBaseName() {
+ return "username_checks";
+ }
+}

View File

@@ -1,23 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/modules/optimizations/EntityGoalSelectorInactiveTickConfig.java
@@ -1,0 +_,20 @@
+package me.earthme.luminol.config.modules.optimizations;
+
+import me.earthme.luminol.config.EnumConfigCategory;
+import me.earthme.luminol.config.IConfigModule;
+import me.earthme.luminol.config.flags.ConfigInfo;
+
+public class EntityGoalSelectorInactiveTickConfig implements IConfigModule {
+ @ConfigInfo(baseName = "enabled")
+ public static boolean enabled = false;
+
+ @Override
+ public EnumConfigCategory getCategory() {
+ return EnumConfigCategory.OPTIMIZATIONS;
+ }
+
+ @Override
+ public String getBaseName() {
+ return "skip_goal_selector_tick_in_inactive_tick";
+ }
+}

View File

@@ -1,23 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/modules/optimizations/GaleVariableEntityWakeupConfig.java
@@ -1,0 +_,20 @@
+package me.earthme.luminol.config.modules.optimizations;
+
+import me.earthme.luminol.config.EnumConfigCategory;
+import me.earthme.luminol.config.IConfigModule;
+import me.earthme.luminol.config.flags.ConfigInfo;
+
+public class GaleVariableEntityWakeupConfig implements IConfigModule {
+ @ConfigInfo(baseName = "entity_wakeup_duration_ratio_standard_deviation")
+ public static double entityWakeUpDurationRatioStandardDeviation = 0.2;
+
+ @Override
+ public EnumConfigCategory getCategory() {
+ return EnumConfigCategory.OPTIMIZATIONS;
+ }
+
+ @Override
+ public String getBaseName() {
+ return "variable_entity_waking_up";
+ }
+}

View File

@@ -1,27 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/modules/optimizations/LobotomizeVillageConfig.java
@@ -1,0 +_,24 @@
+package me.earthme.luminol.config.modules.optimizations;
+
+import me.earthme.luminol.config.EnumConfigCategory;
+import me.earthme.luminol.config.IConfigModule;
+import me.earthme.luminol.config.flags.ConfigInfo;
+
+public class LobotomizeVillageConfig implements IConfigModule {
+ @ConfigInfo(baseName = "enabled")
+ public static boolean villagerLobotomizeEnabled = false;
+ @ConfigInfo(baseName = "check_interval")
+ public static int villagerLobotomizeCheckInterval = 100;
+ @ConfigInfo(baseName = "wait_until_trade_locked")
+ public static boolean villagerLobotomizeWaitUntilTradeLocked = false;
+
+ @Override
+ public EnumConfigCategory getCategory() {
+ return EnumConfigCategory.OPTIMIZATIONS;
+ }
+
+ @Override
+ public String getBaseName() {
+ return "lobotomize_villager";
+ }
+}

View File

@@ -1,25 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/modules/optimizations/PetalReduceSensorWorkConfig.java
@@ -1,0 +_,22 @@
+package me.earthme.luminol.config.modules.optimizations;
+
+import me.earthme.luminol.config.EnumConfigCategory;
+import me.earthme.luminol.config.IConfigModule;
+import me.earthme.luminol.config.flags.ConfigInfo;
+
+public class PetalReduceSensorWorkConfig implements IConfigModule {
+ @ConfigInfo(baseName = "enabled")
+ public static boolean enabled = true;
+ @ConfigInfo(baseName = "delay_ticks")
+ public static int delayTicks = 10;
+
+ @Override
+ public EnumConfigCategory getCategory() {
+ return EnumConfigCategory.OPTIMIZATIONS;
+ }
+
+ @Override
+ public String getBaseName() {
+ return "reduce_sensor_work";
+ }
+}

View File

@@ -1,25 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/modules/optimizations/ProjectileChunkReduceConfig.java
@@ -1,0 +_,22 @@
+package me.earthme.luminol.config.modules.optimizations;
+
+import me.earthme.luminol.config.EnumConfigCategory;
+import me.earthme.luminol.config.IConfigModule;
+import me.earthme.luminol.config.flags.ConfigInfo;
+
+public class ProjectileChunkReduceConfig implements IConfigModule {
+ @ConfigInfo(baseName = "max-loads-per-tick")
+ public static int maxProjectileLoadsPerTick;
+ @ConfigInfo(baseName = "max-loads-per-projectile")
+ public static int maxProjectileLoadsPerProjectile;
+
+ @Override
+ public EnumConfigCategory getCategory() {
+ return EnumConfigCategory.OPTIMIZATIONS;
+ }
+
+ @Override
+ public String getBaseName() {
+ return "projectile";
+ }
+}

View File

@@ -1,23 +0,0 @@
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/config/modules/optimizations/PurpurAlternativeKeepaliveConfig.java
@@ -1,0 +_,20 @@
+package me.earthme.luminol.config.modules.optimizations;
+
+import me.earthme.luminol.config.EnumConfigCategory;
+import me.earthme.luminol.config.IConfigModule;
+import me.earthme.luminol.config.flags.ConfigInfo;
+
+public class PurpurAlternativeKeepaliveConfig implements IConfigModule {
+ @ConfigInfo(baseName = "enabled")
+ public static boolean useAlternateKeepAlive = false;
+
+ @Override
+ public EnumConfigCategory getCategory() {
+ return EnumConfigCategory.OPTIMIZATIONS;
+ }
+
+ @Override
+ public String getBaseName() {
+ return "alternative_keepalive_handling";
+ }
+}

Some files were not shown because too many files have changed in this diff Show More