mirror of
https://github.com/Winds-Studio/Leaf.git
synced 2025-12-25 18:09:17 +00:00
Merge Gale
This commit is contained in:
@@ -1,133 +0,0 @@
|
||||
package gg.pufferfish.pufferfish.sentry;
|
||||
|
||||
import com.google.common.reflect.TypeToken;
|
||||
import com.google.gson.Gson;
|
||||
import io.sentry.Breadcrumb;
|
||||
import io.sentry.Sentry;
|
||||
import io.sentry.SentryEvent;
|
||||
import io.sentry.SentryLevel;
|
||||
import io.sentry.protocol.Message;
|
||||
import io.sentry.protocol.User;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.logging.log4j.Level;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Marker;
|
||||
import org.apache.logging.log4j.core.LogEvent;
|
||||
import org.apache.logging.log4j.core.Logger;
|
||||
import org.apache.logging.log4j.core.appender.AbstractAppender;
|
||||
import org.apache.logging.log4j.core.filter.AbstractFilter;
|
||||
import org.dreeam.leaf.config.modules.misc.SentryDSN;
|
||||
|
||||
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 || !SentryDSN.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, org.apache.logging.log4j.Level level, Marker marker, String msg,
|
||||
Object... params) {
|
||||
return this.filter(logger.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result filter(Logger logger, org.apache.logging.log4j.Level level, Marker marker, Object msg, Throwable t) {
|
||||
return this.filter(logger.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result filter(LogEvent event) {
|
||||
return this.filter(event == null ? null : event.getLoggerName());
|
||||
}
|
||||
|
||||
private Result filter(String loggerName) {
|
||||
return loggerName != null && loggerName.startsWith("gg.castaway.pufferfish.sentry") ? Result.DENY
|
||||
: Result.NEUTRAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
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(org.dreeam.leaf.config.modules.misc.SentryDSN.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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package gg.pufferfish.pufferfish.util;
|
||||
|
||||
import com.google.common.collect.Queues;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
public class AsyncExecutor implements Runnable {
|
||||
|
||||
private final Logger LOGGER = LogManager.getLogger("Leaf");
|
||||
private final Queue<Runnable> jobs = Queues.newArrayDeque();
|
||||
private final Lock mutex = new ReentrantLock();
|
||||
private final Condition cond = mutex.newCondition();
|
||||
private final Thread thread;
|
||||
private volatile boolean killswitch = false;
|
||||
|
||||
public AsyncExecutor(String threadName) {
|
||||
this.thread = new Thread(this, threadName);
|
||||
}
|
||||
|
||||
public void start() {
|
||||
thread.start();
|
||||
}
|
||||
|
||||
public void kill() {
|
||||
killswitch = true;
|
||||
cond.signalAll();
|
||||
}
|
||||
|
||||
public void submit(Runnable runnable) {
|
||||
mutex.lock();
|
||||
try {
|
||||
jobs.offer(runnable);
|
||||
cond.signalAll();
|
||||
} finally {
|
||||
mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (!killswitch) {
|
||||
try {
|
||||
Runnable runnable = takeRunnable();
|
||||
if (runnable != null) {
|
||||
runnable.run();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to execute async job for thread {}", thread.getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Runnable takeRunnable() throws InterruptedException {
|
||||
mutex.lock();
|
||||
try {
|
||||
while (jobs.isEmpty() && !killswitch) {
|
||||
cond.await();
|
||||
}
|
||||
|
||||
if (jobs.isEmpty()) return null; // We've set killswitch
|
||||
|
||||
return jobs.remove();
|
||||
} finally {
|
||||
mutex.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package gg.pufferfish.pufferfish.util;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public record IterableWrapper<T>(Iterator<T> iterator) implements Iterable<T> {
|
||||
@NotNull
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return iterator;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
// Gale - JettPack - reduce array allocations
|
||||
|
||||
package me.titaniumtown;
|
||||
|
||||
public final class ArrayConstants {
|
||||
|
||||
private ArrayConstants() {}
|
||||
|
||||
public static final Object[] emptyObjectArray = new Object[0];
|
||||
public static final short[] emptyShortArray = new short[0];
|
||||
public static final int[] emptyIntArray = new int[0];
|
||||
public static final int[] zeroSingletonIntArray = new int[]{0};
|
||||
public static final byte[] emptyByteArray = new byte[0];
|
||||
public static final String[] emptyStringArray = new String[0];
|
||||
public static final long[] emptyLongArray = new long[0];
|
||||
public static final org.bukkit.entity.Entity[] emptyBukkitEntityArray = new org.bukkit.entity.Entity[0];
|
||||
public static final net.minecraft.world.entity.Entity[] emptyEntityArray = new net.minecraft.world.entity.Entity[0];
|
||||
//public static final net.minecraft.server.level.ServerLevel[] emptyServerLevelArray = new net.minecraft.server.level.ServerLevel[0];
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package net.caffeinemc.mods.lithium.common.entity;
|
||||
|
||||
import net.caffeinemc.mods.lithium.common.util.change_tracking.ChangeSubscriber;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
public interface EquipmentEntity {
|
||||
|
||||
void onEquipmentReplaced(ItemStack oldStack, ItemStack newStack);
|
||||
|
||||
interface EquipmentTrackingEntity {
|
||||
void onEquipmentChanged();
|
||||
}
|
||||
|
||||
interface TickableEnchantmentTrackingEntity extends ChangeSubscriber.EnchantmentSubscriber<ItemStack> {
|
||||
void updateHasTickableEnchantments(ItemStack oldStack, ItemStack newStack);
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package net.caffeinemc.mods.lithium.common.util.change_tracking;
|
||||
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
public interface ChangePublisher<T> {
|
||||
|
||||
void subscribe(ChangeSubscriber<T> subscriber, int subscriberData);
|
||||
|
||||
int unsubscribe(ChangeSubscriber<T> subscriber);
|
||||
|
||||
default void unsubscribeWithData(ChangeSubscriber<T> subscriber, int index) {
|
||||
throw new UnsupportedOperationException("Only implemented for ItemStacks");
|
||||
}
|
||||
|
||||
default boolean isSubscribedWithData(ChangeSubscriber<ItemStack> subscriber, int subscriberData) {
|
||||
throw new UnsupportedOperationException("Only implemented for ItemStacks");
|
||||
}
|
||||
}
|
||||
@@ -1,195 +0,0 @@
|
||||
package net.caffeinemc.mods.lithium.common.util.change_tracking;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
public interface ChangeSubscriber<T> {
|
||||
|
||||
static <T> ChangeSubscriber<T> combine(ChangeSubscriber<T> prevSubscriber, int prevSData, @NotNull ChangeSubscriber<T> newSubscriber, int newSData) {
|
||||
if (prevSubscriber == null) {
|
||||
return newSubscriber;
|
||||
} else if (prevSubscriber instanceof Multi) {
|
||||
ArrayList<ChangeSubscriber<T>> subscribers = new ArrayList<>(((Multi<T>) prevSubscriber).subscribers);
|
||||
IntArrayList subscriberDatas = new IntArrayList(((Multi<T>) prevSubscriber).subscriberDatas);
|
||||
subscribers.add(newSubscriber);
|
||||
subscriberDatas.add(newSData);
|
||||
return new Multi<>(subscribers, subscriberDatas);
|
||||
} else {
|
||||
ArrayList<ChangeSubscriber<T>> subscribers = new ArrayList<>();
|
||||
IntArrayList subscriberDatas = new IntArrayList();
|
||||
subscribers.add(prevSubscriber);
|
||||
subscriberDatas.add(prevSData);
|
||||
subscribers.add(newSubscriber);
|
||||
subscriberDatas.add(newSData);
|
||||
return new Multi<>(subscribers, subscriberDatas);
|
||||
}
|
||||
}
|
||||
|
||||
static <T> ChangeSubscriber<T> without(ChangeSubscriber<T> prevSubscriber, ChangeSubscriber<T> removedSubscriber) {
|
||||
return without(prevSubscriber, removedSubscriber, 0, false);
|
||||
}
|
||||
|
||||
static <T> ChangeSubscriber<T> without(ChangeSubscriber<T> prevSubscriber, ChangeSubscriber<T> removedSubscriber, int removedSubscriberData, boolean matchData) {
|
||||
if (prevSubscriber == removedSubscriber) {
|
||||
return null;
|
||||
} else if (prevSubscriber instanceof Multi<T> multi) {
|
||||
int index = multi.indexOf(removedSubscriber, removedSubscriberData, matchData);
|
||||
if (index != -1) {
|
||||
if (multi.subscribers.size() == 2) {
|
||||
return multi.subscribers.get(1 - index);
|
||||
} else {
|
||||
ArrayList<ChangeSubscriber<T>> subscribers = new ArrayList<>(multi.subscribers);
|
||||
IntArrayList subscriberDatas = new IntArrayList(multi.subscriberDatas);
|
||||
subscribers.remove(index);
|
||||
subscriberDatas.removeInt(index);
|
||||
|
||||
return new Multi<>(subscribers, subscriberDatas);
|
||||
}
|
||||
} else {
|
||||
return prevSubscriber;
|
||||
}
|
||||
} else {
|
||||
return prevSubscriber;
|
||||
}
|
||||
}
|
||||
|
||||
static <T> int dataWithout(ChangeSubscriber<T> prevSubscriber, ChangeSubscriber<T> removedSubscriber, int subscriberData) {
|
||||
return dataWithout(prevSubscriber, removedSubscriber, subscriberData, 0, false);
|
||||
}
|
||||
|
||||
static <T> int dataWithout(ChangeSubscriber<T> prevSubscriber, ChangeSubscriber<T> removedSubscriber, int subscriberData, int removedSubscriberData, boolean matchData) {
|
||||
if (prevSubscriber instanceof Multi<T> multi) {
|
||||
int index = multi.indexOf(removedSubscriber, removedSubscriberData, matchData);
|
||||
if (index != -1) {
|
||||
if (multi.subscribers.size() == 2) {
|
||||
return multi.subscriberDatas.getInt(1 - index);
|
||||
} else {
|
||||
return subscriberData;
|
||||
}
|
||||
} else {
|
||||
return subscriberData;
|
||||
}
|
||||
}
|
||||
return prevSubscriber == removedSubscriber ? 0 : subscriberData;
|
||||
}
|
||||
|
||||
static int dataOf(ChangeSubscriber<?> subscribers, ChangeSubscriber<?> subscriber, int subscriberData) {
|
||||
return subscribers instanceof Multi<?> multi ? multi.subscriberDatas.getInt(multi.subscribers.indexOf(subscriber)) : subscriberData;
|
||||
}
|
||||
|
||||
static boolean containsSubscriber(ChangeSubscriber<ItemStack> subscriber, int subscriberData, ChangeSubscriber<ItemStack> subscriber1, int subscriberData1) {
|
||||
if (subscriber instanceof Multi<ItemStack> multi) {
|
||||
return multi.indexOf(subscriber1, subscriberData1, true) != -1;
|
||||
}
|
||||
return subscriber == subscriber1 && subscriberData == subscriberData1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Notify the subscriber that the publisher will be changed immediately after this call.
|
||||
*
|
||||
* @param publisher The publisher that is about to change
|
||||
* @param subscriberData The data associated with the subscriber, given when the subscriber was added
|
||||
*/
|
||||
void notify(@Nullable T publisher, int subscriberData);
|
||||
|
||||
/**
|
||||
* Notify the subscriber about being unsubscribed from the publisher. Used when the publisher becomes invalid.
|
||||
* The subscriber should not attempt to unsubscribe itself from the publisher in this method.
|
||||
*
|
||||
* @param publisher The publisher unsubscribed from
|
||||
* @param subscriberData The data associated with the subscriber, given when the subscriber was added
|
||||
*/
|
||||
void forceUnsubscribe(T publisher, int subscriberData);
|
||||
|
||||
interface CountChangeSubscriber<T> extends ChangeSubscriber<T> {
|
||||
|
||||
/**
|
||||
* Notify the subscriber that the publisher's count data will be changed immediately after this call.
|
||||
*
|
||||
* @param publisher The publisher that is about to change
|
||||
* @param subscriberData The data associated with the subscriber, given when the subscriber was added
|
||||
* @param newCount The new count of the publisher
|
||||
*/
|
||||
void notifyCount(T publisher, int subscriberData, int newCount);
|
||||
}
|
||||
|
||||
interface EnchantmentSubscriber<T> extends ChangeSubscriber<T> {
|
||||
|
||||
/**
|
||||
* Notify the subscriber that the publisher's enchantment data has been changed immediately before this call.
|
||||
*
|
||||
* @param publisher The publisher that has changed
|
||||
* @param subscriberData The data associated with the subscriber, given when the subscriber was added
|
||||
*/
|
||||
void notifyAfterEnchantmentChange(T publisher, int subscriberData);
|
||||
}
|
||||
|
||||
class Multi<T> implements CountChangeSubscriber<T>, EnchantmentSubscriber<T> {
|
||||
private final ArrayList<ChangeSubscriber<T>> subscribers;
|
||||
private final IntArrayList subscriberDatas;
|
||||
|
||||
public Multi(ArrayList<ChangeSubscriber<T>> subscribers, IntArrayList subscriberDatas) {
|
||||
this.subscribers = subscribers;
|
||||
this.subscriberDatas = subscriberDatas;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notify(T publisher, int subscriberData) {
|
||||
ArrayList<ChangeSubscriber<T>> changeSubscribers = this.subscribers;
|
||||
for (int i = 0; i < changeSubscribers.size(); i++) {
|
||||
ChangeSubscriber<T> subscriber = changeSubscribers.get(i);
|
||||
subscriber.notify(publisher, this.subscriberDatas.getInt(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forceUnsubscribe(T publisher, int subscriberData) {
|
||||
ArrayList<ChangeSubscriber<T>> changeSubscribers = this.subscribers;
|
||||
for (int i = 0; i < changeSubscribers.size(); i++) {
|
||||
ChangeSubscriber<T> subscriber = changeSubscribers.get(i);
|
||||
subscriber.forceUnsubscribe(publisher, this.subscriberDatas.getInt(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyCount(T publisher, int subscriberData, int newCount) {
|
||||
ArrayList<ChangeSubscriber<T>> changeSubscribers = this.subscribers;
|
||||
for (int i = 0; i < changeSubscribers.size(); i++) {
|
||||
ChangeSubscriber<T> subscriber = changeSubscribers.get(i);
|
||||
if (subscriber instanceof ChangeSubscriber.CountChangeSubscriber<T> countChangeSubscriber) {
|
||||
countChangeSubscriber.notifyCount(publisher, this.subscriberDatas.getInt(i), newCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int indexOf(ChangeSubscriber<T> subscriber, int subscriberData, boolean matchData) {
|
||||
if (!matchData) {
|
||||
return this.subscribers.indexOf(subscriber);
|
||||
} else {
|
||||
for (int i = 0; i < this.subscribers.size(); i++) {
|
||||
if (this.subscribers.get(i) == subscriber && this.subscriberDatas.getInt(i) == subscriberData) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyAfterEnchantmentChange(T publisher, int subscriberData) {
|
||||
ArrayList<ChangeSubscriber<T>> changeSubscribers = this.subscribers;
|
||||
for (int i = 0; i < changeSubscribers.size(); i++) {
|
||||
ChangeSubscriber<T> subscriber = changeSubscribers.get(i);
|
||||
if (subscriber instanceof ChangeSubscriber.EnchantmentSubscriber<T> enchantmentSubscriber) {
|
||||
enchantmentSubscriber.notifyAfterEnchantmentChange(publisher, this.subscriberDatas.getInt(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,209 +0,0 @@
|
||||
// Gale - Lithium - faster chunk serialization
|
||||
|
||||
package net.caffeinemc.mods.lithium.common.world.chunk;
|
||||
|
||||
import it.unimi.dsi.fastutil.HashCommon;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
|
||||
import net.minecraft.CrashReport;
|
||||
import net.minecraft.CrashReportCategory;
|
||||
import net.minecraft.ReportedException;
|
||||
import net.minecraft.core.IdMap;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.VarInt;
|
||||
import net.minecraft.world.level.chunk.MissingPaletteEntryException;
|
||||
import net.minecraft.world.level.chunk.Palette;
|
||||
import net.minecraft.world.level.chunk.PaletteResize;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static it.unimi.dsi.fastutil.Hash.FAST_LOAD_FACTOR;
|
||||
|
||||
/**
|
||||
* Generally provides better performance over the vanilla {@link net.minecraft.world.level.chunk.HashMapPalette} when calling
|
||||
* {@link LithiumHashPalette#idFor(Object)} through using a faster backing map and reducing pointer chasing.
|
||||
*/
|
||||
public class LithiumHashPalette<T> implements Palette<T> {
|
||||
private static final int ABSENT_VALUE = -1;
|
||||
|
||||
private final IdMap<T> idList;
|
||||
private final PaletteResize<T> resizeHandler;
|
||||
private final int indexBits;
|
||||
|
||||
private final Reference2IntOpenHashMap<T> table;
|
||||
private T[] entries;
|
||||
private int size = 0;
|
||||
|
||||
private LithiumHashPalette(IdMap<T> idList, PaletteResize<T> resizeHandler, int indexBits, T[] entries, Reference2IntOpenHashMap<T> table, int size) {
|
||||
this.idList = idList;
|
||||
this.resizeHandler = resizeHandler;
|
||||
this.indexBits = indexBits;
|
||||
this.entries = entries;
|
||||
this.table = table;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public LithiumHashPalette(IdMap<T> idList, int bits, PaletteResize<T> resizeHandler, List<T> list) {
|
||||
this(idList, bits, resizeHandler);
|
||||
|
||||
for (T t : list) {
|
||||
this.addEntry(t);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public LithiumHashPalette(IdMap<T> idList, int bits, PaletteResize<T> resizeHandler) {
|
||||
this.idList = idList;
|
||||
this.indexBits = bits;
|
||||
this.resizeHandler = resizeHandler;
|
||||
|
||||
int capacity = 1 << bits;
|
||||
|
||||
this.entries = (T[]) new Object[capacity];
|
||||
this.table = new Reference2IntOpenHashMap<>(capacity, FAST_LOAD_FACTOR);
|
||||
this.table.defaultReturnValue(ABSENT_VALUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int idFor(@NotNull T obj) {
|
||||
int id = this.table.getInt(obj);
|
||||
|
||||
if (id == ABSENT_VALUE) {
|
||||
id = this.computeEntry(obj);
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean maybeHas(@NotNull Predicate<T> predicate) {
|
||||
for (int i = 0; i < this.size; ++i) {
|
||||
if (predicate.test(this.entries[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private int computeEntry(T obj) {
|
||||
int id = this.addEntry(obj);
|
||||
|
||||
if (id >= 1 << this.indexBits) {
|
||||
if (this.resizeHandler == null) {
|
||||
throw new IllegalStateException("Cannot grow");
|
||||
} else {
|
||||
id = this.resizeHandler.onResize(this.indexBits + 1, obj);
|
||||
}
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
private int addEntry(T obj) {
|
||||
int nextId = this.size;
|
||||
|
||||
if (nextId >= this.entries.length) {
|
||||
this.resize(this.size);
|
||||
}
|
||||
|
||||
this.table.put(obj, nextId);
|
||||
this.entries[nextId] = obj;
|
||||
|
||||
this.size++;
|
||||
|
||||
return nextId;
|
||||
}
|
||||
|
||||
private void resize(int neededCapacity) {
|
||||
this.entries = Arrays.copyOf(this.entries, HashCommon.nextPowerOfTwo(neededCapacity + 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull T valueFor(int id) {
|
||||
T[] entries = this.entries;
|
||||
|
||||
T entry = null;
|
||||
if (id >= 0 && id < entries.length) {
|
||||
entry = entries[id];
|
||||
}
|
||||
|
||||
if (entry != null) {
|
||||
return entry;
|
||||
} else {
|
||||
throw this.missingPaletteEntryCrash(id);
|
||||
}
|
||||
}
|
||||
|
||||
private ReportedException missingPaletteEntryCrash(int id) {
|
||||
try {
|
||||
throw new MissingPaletteEntryException(id);
|
||||
} catch (MissingPaletteEntryException e) {
|
||||
CrashReport crashReport = CrashReport.forThrowable(e, "[Lithium] Getting Palette Entry");
|
||||
CrashReportCategory crashReportCategory = crashReport.addCategory("Chunk section");
|
||||
crashReportCategory.setDetail("IndexBits", this.indexBits);
|
||||
crashReportCategory.setDetail("Entries", this.entries.length + " Elements: " + Arrays.toString(this.entries));
|
||||
crashReportCategory.setDetail("Table", this.table.size() + " Elements: " + this.table);
|
||||
return new ReportedException(crashReport);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(FriendlyByteBuf buf) {
|
||||
this.clear();
|
||||
|
||||
int entryCount = buf.readVarInt();
|
||||
|
||||
for (int i = 0; i < entryCount; ++i) {
|
||||
this.addEntry(this.idList.byIdOrThrow(buf.readVarInt()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(FriendlyByteBuf buf) {
|
||||
int size = this.size;
|
||||
buf.writeVarInt(size);
|
||||
|
||||
for (int i = 0; i < size; ++i) {
|
||||
buf.writeVarInt(this.idList.getId(this.valueFor(i)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSerializedSize() {
|
||||
int size = VarInt.getByteSize(this.size);
|
||||
|
||||
for (int i = 0; i < this.size; ++i) {
|
||||
size += VarInt.getByteSize(this.idList.getId(this.valueFor(i)));
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return this.size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Palette<T> copy(@NotNull PaletteResize<T> resizeHandler) {
|
||||
return new LithiumHashPalette<>(this.idList, resizeHandler, this.indexBits, this.entries.clone(), this.table.clone(), this.size);
|
||||
}
|
||||
|
||||
private void clear() {
|
||||
Arrays.fill(this.entries, null);
|
||||
this.table.clear();
|
||||
this.size = 0;
|
||||
}
|
||||
|
||||
public List<T> getElements() {
|
||||
T[] copy = Arrays.copyOf(this.entries, this.size);
|
||||
return Arrays.asList(copy);
|
||||
}
|
||||
|
||||
public static <A> Palette<A> create(int bits, IdMap<A> idList, PaletteResize<A> listener, List<A> list) {
|
||||
return new LithiumHashPalette<>(idList, bits, listener, list);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package org.dreeam.leaf;
|
||||
|
||||
import io.papermc.paper.PaperBootstrap;
|
||||
import joptsimple.OptionSet;
|
||||
|
||||
public class LeafBootstrap {
|
||||
public static final boolean enableFMA = Boolean.parseBoolean(System.getProperty("Leaf.enableFMA", "false")); // Leaf - FMA feature
|
||||
|
||||
public static void boot(final OptionSet options) {
|
||||
runPreBootTasks();
|
||||
|
||||
PaperBootstrap.boot(options);
|
||||
}
|
||||
|
||||
private static void runPreBootTasks() {
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package org.dreeam.leaf.async;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
public class AsyncChunkSending {
|
||||
|
||||
public static final Logger LOGGER = LogManager.getLogger(AsyncChunkSending.class.getSimpleName());
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package org.dreeam.leaf.async;
|
||||
|
||||
import net.minecraft.Util;
|
||||
import org.dreeam.leaf.config.modules.async.AsyncPlayerDataSave;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
public class AsyncPlayerDataSaving {
|
||||
|
||||
private AsyncPlayerDataSaving() {
|
||||
}
|
||||
|
||||
public static void saveAsync(Runnable runnable) {
|
||||
if (!AsyncPlayerDataSave.enabled) {
|
||||
runnable.run();
|
||||
return;
|
||||
}
|
||||
|
||||
ExecutorService ioExecutor = Util.backgroundExecutor().service();
|
||||
|
||||
CompletableFuture.runAsync(runnable, ioExecutor);
|
||||
}
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
package org.dreeam.leaf.async.locate;
|
||||
|
||||
import ca.spottedleaf.moonrise.common.util.TickThread;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.HolderSet;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.level.chunk.ChunkGenerator;
|
||||
import net.minecraft.world.level.levelgen.structure.Structure;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
// Original project: https://github.com/thebrightspark/AsyncLocator
|
||||
public class AsyncLocator {
|
||||
|
||||
private static final ExecutorService LOCATING_EXECUTOR_SERVICE;
|
||||
|
||||
private AsyncLocator() {
|
||||
}
|
||||
|
||||
public static class AsyncLocatorThread extends TickThread {
|
||||
private static final AtomicInteger THREAD_COUNTER = new AtomicInteger(0);
|
||||
|
||||
public AsyncLocatorThread(Runnable run, String name) {
|
||||
super(run, name, THREAD_COUNTER.incrementAndGet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
super.run();
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
int threads = org.dreeam.leaf.config.modules.async.AsyncLocator.asyncLocatorThreads;
|
||||
LOCATING_EXECUTOR_SERVICE = new ThreadPoolExecutor(
|
||||
1,
|
||||
threads,
|
||||
org.dreeam.leaf.config.modules.async.AsyncLocator.asyncLocatorKeepalive,
|
||||
TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(),
|
||||
new ThreadFactoryBuilder()
|
||||
.setThreadFactory(
|
||||
r -> new AsyncLocatorThread(r, "Leaf Async Locator Thread") {
|
||||
@Override
|
||||
public void run() {
|
||||
r.run();
|
||||
}
|
||||
}
|
||||
)
|
||||
.setNameFormat("Leaf Async Locator Thread - %d")
|
||||
.setPriority(Thread.NORM_PRIORITY - 2)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
public static void shutdownExecutorService() {
|
||||
if (LOCATING_EXECUTOR_SERVICE != null) {
|
||||
LOCATING_EXECUTOR_SERVICE.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues a task to locate a feature using {@link ServerLevel#findNearestMapStructure(TagKey, BlockPos, int, boolean)}
|
||||
* and returns a {@link LocateTask} with the futures for it.
|
||||
*/
|
||||
public static LocateTask<BlockPos> locate(
|
||||
ServerLevel level,
|
||||
TagKey<Structure> structureTag,
|
||||
BlockPos pos,
|
||||
int searchRadius,
|
||||
boolean skipKnownStructures
|
||||
) {
|
||||
CompletableFuture<BlockPos> completableFuture = new CompletableFuture<>();
|
||||
Future<?> future = LOCATING_EXECUTOR_SERVICE.submit(
|
||||
() -> doLocateLevel(completableFuture, level, structureTag, pos, searchRadius, skipKnownStructures)
|
||||
);
|
||||
return new LocateTask<>(level.getServer(), completableFuture, future);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues a task to locate a feature using
|
||||
* {@link ChunkGenerator#findNearestMapStructure(ServerLevel, HolderSet, BlockPos, int, boolean)} and returns a
|
||||
* {@link LocateTask} with the futures for it.
|
||||
*/
|
||||
public static LocateTask<Pair<BlockPos, Holder<Structure>>> locate(
|
||||
ServerLevel level,
|
||||
HolderSet<Structure> structureSet,
|
||||
BlockPos pos,
|
||||
int searchRadius,
|
||||
boolean skipKnownStructures
|
||||
) {
|
||||
CompletableFuture<Pair<BlockPos, Holder<Structure>>> completableFuture = new CompletableFuture<>();
|
||||
Future<?> future = LOCATING_EXECUTOR_SERVICE.submit(
|
||||
() -> doLocateChunkGenerator(completableFuture, level, structureSet, pos, searchRadius, skipKnownStructures)
|
||||
);
|
||||
return new LocateTask<>(level.getServer(), completableFuture, future);
|
||||
}
|
||||
|
||||
private static void doLocateLevel(
|
||||
CompletableFuture<BlockPos> completableFuture,
|
||||
ServerLevel level,
|
||||
TagKey<Structure> structureTag,
|
||||
BlockPos pos,
|
||||
int searchRadius,
|
||||
boolean skipExistingChunks
|
||||
) {
|
||||
BlockPos foundPos = level.findNearestMapStructure(structureTag, pos, searchRadius, skipExistingChunks);
|
||||
completableFuture.complete(foundPos);
|
||||
}
|
||||
|
||||
private static void doLocateChunkGenerator(
|
||||
CompletableFuture<Pair<BlockPos, Holder<Structure>>> completableFuture,
|
||||
ServerLevel level,
|
||||
HolderSet<Structure> structureSet,
|
||||
BlockPos pos,
|
||||
int searchRadius,
|
||||
boolean skipExistingChunks
|
||||
) {
|
||||
Pair<BlockPos, Holder<Structure>> foundPair = level.getChunkSource().getGenerator()
|
||||
.findNearestMapStructure(level, structureSet, pos, searchRadius, skipExistingChunks);
|
||||
completableFuture.complete(foundPair);
|
||||
}
|
||||
|
||||
/**
|
||||
* Holder of the futures for an async locate task as well as providing some helper functions.
|
||||
* The completableFuture will be completed once the call to
|
||||
* {@link ServerLevel#findNearestMapStructure(TagKey, BlockPos, int, boolean)} has completed, and will hold the
|
||||
* result of it.
|
||||
* The taskFuture is the future for the {@link Runnable} itself in the executor service.
|
||||
*/
|
||||
public record LocateTask<T>(MinecraftServer server, CompletableFuture<T> completableFuture, Future<?> taskFuture) {
|
||||
/**
|
||||
* Helper function that calls {@link CompletableFuture#thenAccept(Consumer)} with the given action.
|
||||
* Bear in mind that the action will be executed from the task's thread. If you intend to change any game data,
|
||||
* it's strongly advised you use {@link #thenOnServerThread(Consumer)} instead so that it's queued and executed
|
||||
* on the main server thread instead.
|
||||
*/
|
||||
public LocateTask<T> then(Consumer<T> action) {
|
||||
completableFuture.thenAccept(action);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that calls {@link CompletableFuture#thenAccept(Consumer)} with the given action on the server
|
||||
* thread.
|
||||
*/
|
||||
public LocateTask<T> thenOnServerThread(Consumer<T> action) {
|
||||
completableFuture.thenAccept(pos -> server.scheduleOnMain(() -> action.accept(pos)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that cancels both completableFuture and taskFuture.
|
||||
*/
|
||||
public void cancel() {
|
||||
taskFuture.cancel(true);
|
||||
completableFuture.cancel(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,288 +0,0 @@
|
||||
package org.dreeam.leaf.async.path;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.pathfinder.Node;
|
||||
import net.minecraft.world.level.pathfinder.Path;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* i'll be using this to represent a path that not be processed yet!
|
||||
*/
|
||||
public class AsyncPath extends Path {
|
||||
|
||||
/**
|
||||
* marks whether this async path has been processed
|
||||
*/
|
||||
private volatile PathProcessState processState = PathProcessState.WAITING;
|
||||
|
||||
/**
|
||||
* runnables waiting for this to be processed
|
||||
*/
|
||||
private final List<Runnable> postProcessing = new ArrayList<>(0);
|
||||
|
||||
/**
|
||||
* a list of positions that this path could path towards
|
||||
*/
|
||||
private final Set<BlockPos> positions;
|
||||
|
||||
/**
|
||||
* the supplier of the real processed path
|
||||
*/
|
||||
private final Supplier<Path> pathSupplier;
|
||||
|
||||
/*
|
||||
* Processed values
|
||||
*/
|
||||
|
||||
/**
|
||||
* this is a reference to the nodes list in the parent `Path` object
|
||||
*/
|
||||
private final List<Node> nodes;
|
||||
/**
|
||||
* the block we're trying to path to
|
||||
* <p>
|
||||
* while processing, we have no idea where this is so consumers of `Path` should check that the path is processed before checking the target block
|
||||
*/
|
||||
private @Nullable BlockPos target;
|
||||
/**
|
||||
* how far we are to the target
|
||||
* <p>
|
||||
* while processing, the target could be anywhere but theoretically we're always "close" to a theoretical target so default is 0
|
||||
*/
|
||||
private float distToTarget = 0;
|
||||
/**
|
||||
* whether we can reach the target
|
||||
* <p>
|
||||
* while processing, we can always theoretically reach the target so default is true
|
||||
*/
|
||||
private boolean canReach = true;
|
||||
|
||||
public AsyncPath(@NotNull List<Node> emptyNodeList, @NotNull Set<BlockPos> positions, @NotNull Supplier<Path> pathSupplier) {
|
||||
//noinspection ConstantConditions
|
||||
super(emptyNodeList, null, false);
|
||||
|
||||
this.nodes = emptyNodeList;
|
||||
this.positions = positions;
|
||||
this.pathSupplier = pathSupplier;
|
||||
|
||||
AsyncPathProcessor.queue(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isProcessed() {
|
||||
return this.processState == PathProcessState.COMPLETED;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the future representing the processing state of this path
|
||||
*/
|
||||
public synchronized void postProcessing(@NotNull Runnable runnable) {
|
||||
if (isProcessed()) {
|
||||
runnable.run();
|
||||
} else {
|
||||
this.postProcessing.add(runnable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* an easy way to check if this processing path is the same as an attempted new path
|
||||
*
|
||||
* @param positions - the positions to compare against
|
||||
* @return true if we are processing the same positions
|
||||
*/
|
||||
public boolean hasSameProcessingPositions(final Set<BlockPos> positions) {
|
||||
if (this.positions.size() != positions.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.positions.containsAll(positions);
|
||||
}
|
||||
|
||||
/**
|
||||
* starts processing this path
|
||||
*/
|
||||
public synchronized void process() {
|
||||
if (this.processState == PathProcessState.COMPLETED ||
|
||||
this.processState == PathProcessState.PROCESSING) {
|
||||
return;
|
||||
}
|
||||
|
||||
processState = PathProcessState.PROCESSING;
|
||||
|
||||
final Path bestPath = this.pathSupplier.get();
|
||||
|
||||
this.nodes.addAll(bestPath.nodes); // we mutate this list to reuse the logic in Path
|
||||
this.target = bestPath.getTarget();
|
||||
this.distToTarget = bestPath.getDistToTarget();
|
||||
this.canReach = bestPath.canReach();
|
||||
|
||||
processState = PathProcessState.COMPLETED;
|
||||
|
||||
for (Runnable runnable : this.postProcessing) {
|
||||
runnable.run();
|
||||
} // Run tasks after processing
|
||||
}
|
||||
|
||||
/**
|
||||
* if this path is accessed while it hasn't processed, just process it in-place
|
||||
*/
|
||||
private void checkProcessed() {
|
||||
if (this.processState == PathProcessState.WAITING ||
|
||||
this.processState == PathProcessState.PROCESSING) { // Block if we are on processing
|
||||
this.process();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* overrides we need for final fields that we cannot modify after processing
|
||||
*/
|
||||
|
||||
@Override
|
||||
public @NotNull BlockPos getTarget() {
|
||||
this.checkProcessed();
|
||||
|
||||
return this.target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getDistToTarget() {
|
||||
this.checkProcessed();
|
||||
|
||||
return this.distToTarget;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canReach() {
|
||||
this.checkProcessed();
|
||||
|
||||
return this.canReach;
|
||||
}
|
||||
|
||||
/*
|
||||
* overrides to ensure we're processed first
|
||||
*/
|
||||
|
||||
@Override
|
||||
public boolean isDone() {
|
||||
return this.processState == PathProcessState.COMPLETED && super.isDone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void advance() {
|
||||
this.checkProcessed();
|
||||
|
||||
super.advance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean notStarted() {
|
||||
this.checkProcessed();
|
||||
|
||||
return super.notStarted();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Node getEndNode() {
|
||||
this.checkProcessed();
|
||||
|
||||
return super.getEndNode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getNode(int index) {
|
||||
this.checkProcessed();
|
||||
|
||||
return super.getNode(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void truncateNodes(int length) {
|
||||
this.checkProcessed();
|
||||
|
||||
super.truncateNodes(length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceNode(int index, Node node) {
|
||||
this.checkProcessed();
|
||||
|
||||
super.replaceNode(index, node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNodeCount() {
|
||||
this.checkProcessed();
|
||||
|
||||
return super.getNodeCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNextNodeIndex() {
|
||||
this.checkProcessed();
|
||||
|
||||
return super.getNextNodeIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNextNodeIndex(int nodeIndex) {
|
||||
this.checkProcessed();
|
||||
|
||||
super.setNextNodeIndex(nodeIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec3 getEntityPosAtNode(Entity entity, int index) {
|
||||
this.checkProcessed();
|
||||
|
||||
return super.getEntityPosAtNode(entity, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockPos getNodePos(int index) {
|
||||
this.checkProcessed();
|
||||
|
||||
return super.getNodePos(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec3 getNextEntityPos(Entity entity) {
|
||||
this.checkProcessed();
|
||||
|
||||
return super.getNextEntityPos(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockPos getNextNodePos() {
|
||||
this.checkProcessed();
|
||||
|
||||
return super.getNextNodePos();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getNextNode() {
|
||||
this.checkProcessed();
|
||||
|
||||
return super.getNextNode();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Node getPreviousNode() {
|
||||
this.checkProcessed();
|
||||
|
||||
return super.getPreviousNode();
|
||||
}
|
||||
|
||||
public PathProcessState getProcessState() {
|
||||
return processState;
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
package org.dreeam.leaf.async.path;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.world.level.pathfinder.Path;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* used to handle the scheduling of async path processing
|
||||
*/
|
||||
public class AsyncPathProcessor {
|
||||
|
||||
private static final String THREAD_PREFIX = "Leaf Async Pathfinding";
|
||||
private static final Logger LOGGER = LogManager.getLogger(THREAD_PREFIX);
|
||||
private static long lastWarnMillis = System.currentTimeMillis();
|
||||
private static final ThreadPoolExecutor pathProcessingExecutor = new ThreadPoolExecutor(
|
||||
1,
|
||||
org.dreeam.leaf.config.modules.async.AsyncPathfinding.asyncPathfindingMaxThreads,
|
||||
org.dreeam.leaf.config.modules.async.AsyncPathfinding.asyncPathfindingKeepalive, TimeUnit.SECONDS,
|
||||
getQueueImpl(),
|
||||
new ThreadFactoryBuilder()
|
||||
.setNameFormat(THREAD_PREFIX + " Thread - %d")
|
||||
.setPriority(Thread.NORM_PRIORITY - 2)
|
||||
.build(),
|
||||
new RejectedTaskHandler()
|
||||
);
|
||||
|
||||
private static class RejectedTaskHandler implements RejectedExecutionHandler {
|
||||
@Override
|
||||
public void rejectedExecution(Runnable rejectedTask, ThreadPoolExecutor executor) {
|
||||
BlockingQueue<Runnable> workQueue = executor.getQueue();
|
||||
if (!executor.isShutdown()) {
|
||||
switch (org.dreeam.leaf.config.modules.async.AsyncPathfinding.asyncPathfindingRejectPolicy) {
|
||||
case FLUSH_ALL -> {
|
||||
if (!workQueue.isEmpty()) {
|
||||
List<Runnable> pendingTasks = new ArrayList<>(workQueue.size());
|
||||
|
||||
workQueue.drainTo(pendingTasks);
|
||||
|
||||
for (Runnable pendingTask : pendingTasks) {
|
||||
pendingTask.run();
|
||||
}
|
||||
}
|
||||
rejectedTask.run();
|
||||
}
|
||||
case CALLER_RUNS -> rejectedTask.run();
|
||||
}
|
||||
}
|
||||
|
||||
if (System.currentTimeMillis() - lastWarnMillis > 30000L) {
|
||||
LOGGER.warn("Async pathfinding processor is busy! Pathfinding tasks will be treated as policy defined in config. Increasing max-threads in Leaf config may help.");
|
||||
lastWarnMillis = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static CompletableFuture<Void> queue(@NotNull AsyncPath path) {
|
||||
return CompletableFuture.runAsync(path::process, pathProcessingExecutor)
|
||||
.orTimeout(60L, TimeUnit.SECONDS)
|
||||
.exceptionally(throwable -> {
|
||||
if (throwable instanceof TimeoutException e) {
|
||||
LOGGER.warn("Async Pathfinding process timed out", e);
|
||||
} else LOGGER.warn("Error occurred while processing async path", throwable);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* takes a possibly unprocessed path, and waits until it is completed
|
||||
* the consumer will be immediately invoked if the path is already processed
|
||||
* the consumer will always be called on the main thread
|
||||
*
|
||||
* @param path a path to wait on
|
||||
* @param afterProcessing a consumer to be called
|
||||
*/
|
||||
public static void awaitProcessing(@Nullable Path path, Consumer<@Nullable Path> afterProcessing) {
|
||||
if (path != null && !path.isProcessed() && path instanceof AsyncPath asyncPath) {
|
||||
asyncPath.postProcessing(() ->
|
||||
MinecraftServer.getServer().scheduleOnMain(() -> afterProcessing.accept(path))
|
||||
);
|
||||
} else {
|
||||
afterProcessing.accept(path);
|
||||
}
|
||||
}
|
||||
|
||||
private static BlockingQueue<Runnable> getQueueImpl() {
|
||||
final int queueCapacity = org.dreeam.leaf.config.modules.async.AsyncPathfinding.asyncPathfindingQueueSize;
|
||||
|
||||
return new LinkedBlockingQueue<>(queueCapacity);
|
||||
}}
|
||||
@@ -1,46 +0,0 @@
|
||||
package org.dreeam.leaf.async.path;
|
||||
|
||||
import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue;
|
||||
import net.minecraft.world.level.pathfinder.NodeEvaluator;
|
||||
|
||||
import org.apache.commons.lang.Validate;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class NodeEvaluatorCache {
|
||||
|
||||
private static final Map<NodeEvaluatorFeatures, MultiThreadedQueue<NodeEvaluator>> threadLocalNodeEvaluators = new ConcurrentHashMap<>();
|
||||
private static final Map<NodeEvaluator, NodeEvaluatorGenerator> nodeEvaluatorToGenerator = new ConcurrentHashMap<>();
|
||||
|
||||
private static @NotNull Queue<NodeEvaluator> getQueueForFeatures(@NotNull NodeEvaluatorFeatures nodeEvaluatorFeatures) {
|
||||
return threadLocalNodeEvaluators.computeIfAbsent(nodeEvaluatorFeatures, key -> new MultiThreadedQueue<>());
|
||||
}
|
||||
|
||||
public static @NotNull NodeEvaluator takeNodeEvaluator(@NotNull NodeEvaluatorGenerator generator, @NotNull NodeEvaluator localNodeEvaluator) {
|
||||
final NodeEvaluatorFeatures nodeEvaluatorFeatures = NodeEvaluatorFeatures.fromNodeEvaluator(localNodeEvaluator);
|
||||
NodeEvaluator nodeEvaluator = getQueueForFeatures(nodeEvaluatorFeatures).poll();
|
||||
|
||||
if (nodeEvaluator == null) {
|
||||
nodeEvaluator = generator.generate(nodeEvaluatorFeatures);
|
||||
}
|
||||
|
||||
nodeEvaluatorToGenerator.put(nodeEvaluator, generator);
|
||||
|
||||
return nodeEvaluator;
|
||||
}
|
||||
|
||||
public static void returnNodeEvaluator(@NotNull NodeEvaluator nodeEvaluator) {
|
||||
final NodeEvaluatorGenerator generator = nodeEvaluatorToGenerator.remove(nodeEvaluator);
|
||||
Validate.notNull(generator, "NodeEvaluator already returned");
|
||||
|
||||
final NodeEvaluatorFeatures nodeEvaluatorFeatures = NodeEvaluatorFeatures.fromNodeEvaluator(nodeEvaluator);
|
||||
getQueueForFeatures(nodeEvaluatorFeatures).offer(nodeEvaluator);
|
||||
}
|
||||
|
||||
public static void removeNodeEvaluator(@NotNull NodeEvaluator nodeEvaluator) {
|
||||
nodeEvaluatorToGenerator.remove(nodeEvaluator);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package org.dreeam.leaf.async.path;
|
||||
|
||||
import net.minecraft.world.level.pathfinder.NodeEvaluator;
|
||||
import net.minecraft.world.level.pathfinder.SwimNodeEvaluator;
|
||||
|
||||
public record NodeEvaluatorFeatures(
|
||||
NodeEvaluatorType type,
|
||||
boolean canPassDoors,
|
||||
boolean canFloat,
|
||||
boolean canWalkOverFences,
|
||||
boolean canOpenDoors,
|
||||
boolean allowBreaching
|
||||
) {
|
||||
public static NodeEvaluatorFeatures fromNodeEvaluator(NodeEvaluator nodeEvaluator) {
|
||||
NodeEvaluatorType type = NodeEvaluatorType.fromNodeEvaluator(nodeEvaluator);
|
||||
boolean canPassDoors = nodeEvaluator.canPassDoors();
|
||||
boolean canFloat = nodeEvaluator.canFloat();
|
||||
boolean canWalkOverFences = nodeEvaluator.canWalkOverFences();
|
||||
boolean canOpenDoors = nodeEvaluator.canOpenDoors();
|
||||
boolean allowBreaching = nodeEvaluator instanceof SwimNodeEvaluator swimNodeEvaluator && swimNodeEvaluator.allowBreaching;
|
||||
return new NodeEvaluatorFeatures(type, canPassDoors, canFloat, canWalkOverFences, canOpenDoors, allowBreaching);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package org.dreeam.leaf.async.path;
|
||||
|
||||
import net.minecraft.world.level.pathfinder.NodeEvaluator;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface NodeEvaluatorGenerator {
|
||||
@NotNull NodeEvaluator generate(NodeEvaluatorFeatures nodeEvaluatorFeatures);
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package org.dreeam.leaf.async.path;
|
||||
|
||||
import net.minecraft.world.level.pathfinder.*;
|
||||
|
||||
public enum NodeEvaluatorType {
|
||||
WALK,
|
||||
SWIM,
|
||||
AMPHIBIOUS,
|
||||
FLY;
|
||||
|
||||
public static NodeEvaluatorType fromNodeEvaluator(NodeEvaluator nodeEvaluator) {
|
||||
if (nodeEvaluator instanceof SwimNodeEvaluator) return SWIM;
|
||||
if (nodeEvaluator instanceof FlyNodeEvaluator) return FLY;
|
||||
if (nodeEvaluator instanceof AmphibiousNodeEvaluator) return AMPHIBIOUS;
|
||||
return WALK;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package org.dreeam.leaf.async.path;
|
||||
|
||||
public enum PathProcessState {
|
||||
WAITING,
|
||||
PROCESSING,
|
||||
COMPLETED,
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package org.dreeam.leaf.async.path;
|
||||
|
||||
import org.dreeam.leaf.config.LeafConfig;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public enum PathfindTaskRejectPolicy {
|
||||
FLUSH_ALL,
|
||||
CALLER_RUNS;
|
||||
|
||||
public static PathfindTaskRejectPolicy fromString(String policy) {
|
||||
try {
|
||||
return PathfindTaskRejectPolicy.valueOf(policy.toUpperCase(Locale.ROOT));
|
||||
} catch (IllegalArgumentException e) {
|
||||
LeafConfig.LOGGER.warn("Invalid pathfind task reject policy: {}, falling back to {}.", policy, FLUSH_ALL.toString());
|
||||
return FLUSH_ALL;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
package org.dreeam.leaf.async.tracker;
|
||||
|
||||
import ca.spottedleaf.moonrise.common.list.ReferenceList;
|
||||
import ca.spottedleaf.moonrise.common.misc.NearbyPlayers;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel;
|
||||
import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup;
|
||||
import ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import net.minecraft.server.level.ChunkMap;
|
||||
import net.minecraft.server.level.FullChunkStatus;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
public class MultithreadedTracker {
|
||||
|
||||
private static final String THREAD_PREFIX = "Leaf Async Tracker";
|
||||
private static final Logger LOGGER = LogManager.getLogger(THREAD_PREFIX);
|
||||
private static long lastWarnMillis = System.currentTimeMillis();
|
||||
private static final ThreadPoolExecutor trackerExecutor = new ThreadPoolExecutor(
|
||||
getCorePoolSize(),
|
||||
getMaxPoolSize(),
|
||||
getKeepAliveTime(), TimeUnit.SECONDS,
|
||||
getQueueImpl(),
|
||||
getThreadFactory(),
|
||||
getRejectedPolicy()
|
||||
);
|
||||
|
||||
private MultithreadedTracker() {
|
||||
}
|
||||
|
||||
public static Executor getTrackerExecutor() {
|
||||
return trackerExecutor;
|
||||
}
|
||||
|
||||
public static void tick(ChunkSystemServerLevel level) {
|
||||
try {
|
||||
if (!org.dreeam.leaf.config.modules.async.MultithreadedTracker.compatModeEnabled) {
|
||||
tickAsync(level);
|
||||
} else {
|
||||
tickAsyncWithCompatMode(level);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Error occurred while executing async task.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void tickAsync(ChunkSystemServerLevel level) {
|
||||
final NearbyPlayers nearbyPlayers = level.moonrise$getNearbyPlayers();
|
||||
final ServerEntityLookup entityLookup = (ServerEntityLookup) level.moonrise$getEntityLookup();
|
||||
|
||||
final ReferenceList<Entity> trackerEntities = entityLookup.trackerEntities;
|
||||
final Entity[] trackerEntitiesRaw = trackerEntities.getRawDataUnchecked();
|
||||
|
||||
// Move tracking to off-main
|
||||
trackerExecutor.execute(() -> {
|
||||
for (final Entity entity : trackerEntitiesRaw) {
|
||||
if (entity == null) continue;
|
||||
|
||||
final ChunkMap.TrackedEntity tracker = ((EntityTrackerEntity) entity).moonrise$getTrackedEntity();
|
||||
|
||||
if (tracker == null) continue;
|
||||
|
||||
tracker.moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition()));
|
||||
tracker.serverEntity.sendChanges();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void tickAsyncWithCompatMode(ChunkSystemServerLevel level) {
|
||||
final NearbyPlayers nearbyPlayers = level.moonrise$getNearbyPlayers();
|
||||
final ServerEntityLookup entityLookup = (ServerEntityLookup) level.moonrise$getEntityLookup();
|
||||
|
||||
final ReferenceList<Entity> trackerEntities = entityLookup.trackerEntities;
|
||||
final Entity[] trackerEntitiesRaw = trackerEntities.getRawDataUnchecked();
|
||||
final Runnable[] sendChangesTasks = new Runnable[trackerEntitiesRaw.length];
|
||||
int index = 0;
|
||||
|
||||
for (final Entity entity : trackerEntitiesRaw) {
|
||||
if (entity == null) continue;
|
||||
|
||||
final ChunkMap.TrackedEntity tracker = ((EntityTrackerEntity) entity).moonrise$getTrackedEntity();
|
||||
|
||||
if (tracker == null) continue;
|
||||
|
||||
tracker.moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition()));
|
||||
sendChangesTasks[index++] = () -> tracker.serverEntity.sendChanges(); // Collect send changes to task array
|
||||
}
|
||||
|
||||
// batch submit tasks
|
||||
trackerExecutor.execute(() -> {
|
||||
for (final Runnable sendChanges : sendChangesTasks) {
|
||||
if (sendChanges == null) continue;
|
||||
|
||||
sendChanges.run();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Original ChunkMap#newTrackerTick of Paper
|
||||
// Just for diff usage for future update
|
||||
private static void tickOriginal(ServerLevel level) {
|
||||
final ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup entityLookup = (ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup) ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel) level).moonrise$getEntityLookup();
|
||||
|
||||
final ca.spottedleaf.moonrise.common.list.ReferenceList<net.minecraft.world.entity.Entity> trackerEntities = entityLookup.trackerEntities;
|
||||
final Entity[] trackerEntitiesRaw = trackerEntities.getRawDataUnchecked();
|
||||
for (int i = 0, len = trackerEntities.size(); i < len; ++i) {
|
||||
final Entity entity = trackerEntitiesRaw[i];
|
||||
final ChunkMap.TrackedEntity tracker = ((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity) entity).moonrise$getTrackedEntity();
|
||||
if (tracker == null) {
|
||||
continue;
|
||||
}
|
||||
((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity) tracker).moonrise$tick(((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity) entity).moonrise$getChunkData().nearbyPlayers);
|
||||
if (((ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity) tracker).moonrise$hasPlayers()
|
||||
|| ((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity) entity).moonrise$getChunkStatus().isOrAfter(FullChunkStatus.ENTITY_TICKING)) {
|
||||
tracker.serverEntity.sendChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int getCorePoolSize() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int getMaxPoolSize() {
|
||||
return org.dreeam.leaf.config.modules.async.MultithreadedTracker.asyncEntityTrackerMaxThreads;
|
||||
}
|
||||
|
||||
private static long getKeepAliveTime() {
|
||||
return org.dreeam.leaf.config.modules.async.MultithreadedTracker.asyncEntityTrackerKeepalive;
|
||||
}
|
||||
|
||||
private static BlockingQueue<Runnable> getQueueImpl() {
|
||||
final int queueCapacity = org.dreeam.leaf.config.modules.async.MultithreadedTracker.asyncEntityTrackerQueueSize;
|
||||
|
||||
return new LinkedBlockingQueue<>(queueCapacity);
|
||||
}
|
||||
|
||||
private static @NotNull ThreadFactory getThreadFactory() {
|
||||
return new ThreadFactoryBuilder()
|
||||
.setThreadFactory(MultithreadedTrackerThread::new)
|
||||
.setNameFormat(THREAD_PREFIX + " Thread - %d")
|
||||
.setPriority(Thread.NORM_PRIORITY - 2)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static @NotNull RejectedExecutionHandler getRejectedPolicy() {
|
||||
return (rejectedTask, executor) -> {
|
||||
BlockingQueue<Runnable> workQueue = executor.getQueue();
|
||||
|
||||
if (!executor.isShutdown()) {
|
||||
if (!workQueue.isEmpty()) {
|
||||
List<Runnable> pendingTasks = new ArrayList<>(workQueue.size());
|
||||
|
||||
workQueue.drainTo(pendingTasks);
|
||||
|
||||
for (Runnable pendingTask : pendingTasks) {
|
||||
pendingTask.run();
|
||||
}
|
||||
}
|
||||
|
||||
rejectedTask.run();
|
||||
}
|
||||
|
||||
if (System.currentTimeMillis() - lastWarnMillis > 30000L) {
|
||||
LOGGER.warn("Async entity tracker is busy! Tracking tasks will be done in the server thread. Increasing max-threads in Leaf config may help.");
|
||||
lastWarnMillis = System.currentTimeMillis();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static class MultithreadedTrackerThread extends Thread {
|
||||
|
||||
public MultithreadedTrackerThread(Runnable runnable) {
|
||||
super(runnable);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package org.dreeam.leaf.async.world;
|
||||
|
||||
import ca.spottedleaf.moonrise.common.util.TickThread;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
public class SparklyPaperServerLevelTickExecutorThreadFactory implements ThreadFactory {
|
||||
|
||||
private final String worldName;
|
||||
|
||||
public SparklyPaperServerLevelTickExecutorThreadFactory(final String worldName) {
|
||||
this.worldName = worldName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Thread newThread(@NotNull Runnable runnable) {
|
||||
TickThread.ServerLevelTickThread tickThread = new TickThread.ServerLevelTickThread(runnable, "Leaf World Ticking Thread - " + this.worldName);
|
||||
|
||||
if (tickThread.isDaemon()) {
|
||||
tickThread.setDaemon(false);
|
||||
}
|
||||
|
||||
if (tickThread.getPriority() != 5) {
|
||||
tickThread.setPriority(5);
|
||||
}
|
||||
|
||||
return tickThread;
|
||||
}
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
package org.dreeam.leaf.command;
|
||||
|
||||
import io.papermc.paper.command.CommandUtil;
|
||||
import it.unimi.dsi.fastutil.Pair;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.minecraft.Util;
|
||||
import org.dreeam.leaf.command.subcommands.MSPTCommand;
|
||||
import org.dreeam.leaf.command.subcommands.ReloadCommand;
|
||||
import org.dreeam.leaf.command.subcommands.VersionCommand;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.permissions.Permission;
|
||||
import org.bukkit.permissions.PermissionDefault;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class LeafCommand extends Command {
|
||||
|
||||
public static final String COMMAND_LABEL = "leaf";
|
||||
public static final String BASE_PERM = LeafCommands.COMMAND_BASE_PERM + "." + COMMAND_LABEL;
|
||||
private static final Permission basePermission = new Permission(BASE_PERM, PermissionDefault.OP);
|
||||
// subcommand label -> subcommand
|
||||
private static final LeafSubcommand RELOAD_SUBCOMMAND = new ReloadCommand();
|
||||
private static final LeafSubcommand VERSION_SUBCOMMAND = new VersionCommand();
|
||||
private static final LeafSubcommand MSPT_SUBCOMMAND = new MSPTCommand();
|
||||
private static final Map<String, LeafSubcommand> SUBCOMMANDS = Util.make(() -> {
|
||||
final Map<Set<String>, LeafSubcommand> commands = new HashMap<>();
|
||||
|
||||
commands.put(Set.of(ReloadCommand.LITERAL_ARGUMENT), RELOAD_SUBCOMMAND);
|
||||
commands.put(Set.of(VersionCommand.LITERAL_ARGUMENT), VERSION_SUBCOMMAND);
|
||||
commands.put(Set.of(MSPTCommand.LITERAL_ARGUMENT), MSPT_SUBCOMMAND);
|
||||
|
||||
return commands.entrySet().stream()
|
||||
.flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue())))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
});
|
||||
// alias -> subcommand label
|
||||
private static final Map<String, String> ALIASES = Util.make(() -> {
|
||||
final Map<String, Set<String>> aliases = new HashMap<>();
|
||||
|
||||
aliases.put(VersionCommand.LITERAL_ARGUMENT, Set.of("ver"));
|
||||
|
||||
return aliases.entrySet().stream()
|
||||
.flatMap(entry -> entry.getValue().stream().map(s -> Map.entry(s, entry.getKey())))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
});
|
||||
|
||||
private String createUsageMessage(Collection<String> arguments) {
|
||||
return "/" + COMMAND_LABEL + " [" + String.join(" | ", arguments) + "]";
|
||||
}
|
||||
|
||||
public LeafCommand() {
|
||||
super(COMMAND_LABEL);
|
||||
this.description = "Leaf related commands";
|
||||
this.usageMessage = this.createUsageMessage(SUBCOMMANDS.keySet());
|
||||
final List<Permission> permissions = SUBCOMMANDS.values().stream().map(LeafSubcommand::getPermission).filter(Objects::nonNull).toList();
|
||||
this.setPermission(BASE_PERM);
|
||||
final PluginManager pluginManager = Bukkit.getServer().getPluginManager();
|
||||
pluginManager.addPermission(basePermission);
|
||||
for (final Permission permission : permissions) {
|
||||
pluginManager.addPermission(permission);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<String> tabComplete(
|
||||
final @NotNull CommandSender sender,
|
||||
final @NotNull String alias,
|
||||
final String[] args,
|
||||
final @Nullable Location location
|
||||
) throws IllegalArgumentException {
|
||||
if (args.length <= 1) {
|
||||
List<String> subCommandArguments = new ArrayList<>(SUBCOMMANDS.size());
|
||||
|
||||
for (Map.Entry<String, LeafSubcommand> subCommandEntry : SUBCOMMANDS.entrySet()) {
|
||||
if (subCommandEntry.getValue().testPermission(sender)) {
|
||||
subCommandArguments.add(subCommandEntry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
return CommandUtil.getListMatchingLast(sender, args, subCommandArguments);
|
||||
}
|
||||
|
||||
final @Nullable Pair<String, LeafSubcommand> subCommand = resolveCommand(args[0]);
|
||||
|
||||
if (subCommand != null && subCommand.second().testPermission(sender)) {
|
||||
return subCommand.second().tabComplete(sender, subCommand.first(), Arrays.copyOfRange(args, 1, args.length));
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private boolean testHasOnePermission(CommandSender sender) {
|
||||
for (Map.Entry<String, LeafSubcommand> subCommandEntry : SUBCOMMANDS.entrySet()) {
|
||||
if (subCommandEntry.getValue().testPermission(sender)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute(
|
||||
final CommandSender sender,
|
||||
final @NotNull String commandLabel,
|
||||
final String[] args
|
||||
) {
|
||||
// Check if the sender has the base permission and at least one specific permission
|
||||
if (!sender.hasPermission(basePermission) || !this.testHasOnePermission(sender)) {
|
||||
sender.sendMessage(Bukkit.permissionMessage());
|
||||
return true;
|
||||
}
|
||||
|
||||
// Determine the usage message with the subcommands they can perform
|
||||
List<String> subCommandArguments = new ArrayList<>(SUBCOMMANDS.size());
|
||||
for (Map.Entry<String, LeafSubcommand> subCommandEntry : SUBCOMMANDS.entrySet()) {
|
||||
if (subCommandEntry.getValue().testPermission(sender)) {
|
||||
subCommandArguments.add(subCommandEntry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
String specificUsageMessage = this.createUsageMessage(subCommandArguments);
|
||||
|
||||
// If they did not give a subcommand
|
||||
if (args.length == 0) {
|
||||
sender.sendMessage(Component.text("Command usage: " + specificUsageMessage, NamedTextColor.GRAY));
|
||||
return false;
|
||||
}
|
||||
|
||||
// If they do not have permission for the subcommand they gave, or the argument is not a valid subcommand
|
||||
final @Nullable Pair<String, LeafSubcommand> subCommand = resolveCommand(args[0]);
|
||||
if (subCommand == null || !subCommand.second().testPermission(sender)) {
|
||||
sender.sendMessage(Component.text("Usage: " + specificUsageMessage, NamedTextColor.RED));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Execute the subcommand
|
||||
final String[] choppedArgs = Arrays.copyOfRange(args, 1, args.length);
|
||||
return subCommand.second().execute(sender, subCommand.first(), choppedArgs);
|
||||
|
||||
}
|
||||
|
||||
private static @Nullable Pair<String, LeafSubcommand> resolveCommand(String label) {
|
||||
label = label.toLowerCase(Locale.ENGLISH);
|
||||
@Nullable LeafSubcommand subCommand = SUBCOMMANDS.get(label);
|
||||
if (subCommand == null) {
|
||||
final @Nullable String command = ALIASES.get(label);
|
||||
if (command != null) {
|
||||
label = command;
|
||||
subCommand = SUBCOMMANDS.get(command);
|
||||
}
|
||||
}
|
||||
|
||||
if (subCommand != null) {
|
||||
return Pair.of(label, subCommand);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package org.dreeam.leaf.command;
|
||||
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.framework.qual.DefaultQualifier;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.craftbukkit.util.permissions.CraftDefaultPermissions;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@DefaultQualifier(NonNull.class)
|
||||
public final class LeafCommands {
|
||||
|
||||
public static final String COMMAND_BASE_PERM = CraftDefaultPermissions.LEAF_ROOT + ".command";
|
||||
|
||||
private LeafCommands() {
|
||||
}
|
||||
|
||||
private static final Map<String, Command> COMMANDS = new HashMap<>();
|
||||
|
||||
static {
|
||||
COMMANDS.put(LeafCommand.COMMAND_LABEL, new LeafCommand());
|
||||
}
|
||||
|
||||
public static void registerCommands(final MinecraftServer server) {
|
||||
COMMANDS.forEach((s, command) -> server.server.getCommandMap().register(s, "Leaf", command));
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package org.dreeam.leaf.command;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.framework.qual.DefaultQualifier;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.permissions.Permission;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@DefaultQualifier(NonNull.class)
|
||||
public interface LeafSubcommand {
|
||||
|
||||
boolean execute(CommandSender sender, String subCommand, String[] args);
|
||||
|
||||
default List<String> tabComplete(final CommandSender sender, final String subCommand, final String[] args) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
boolean testPermission(CommandSender sender);
|
||||
|
||||
@Nullable Permission getPermission();
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package org.dreeam.leaf.command;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.permissions.Permission;
|
||||
import org.bukkit.permissions.PermissionDefault;
|
||||
|
||||
public abstract class PermissionedLeafSubcommand implements LeafSubcommand {
|
||||
|
||||
public final Permission permission;
|
||||
|
||||
protected PermissionedLeafSubcommand(Permission permission) {
|
||||
this.permission = permission;
|
||||
}
|
||||
|
||||
protected PermissionedLeafSubcommand(String permission, PermissionDefault permissionDefault) {
|
||||
this(new Permission(permission, permissionDefault));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean testPermission(@NotNull CommandSender sender) {
|
||||
return sender.hasPermission(this.permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Permission getPermission() {
|
||||
return this.permission;
|
||||
}
|
||||
}
|
||||
@@ -1,244 +0,0 @@
|
||||
package org.dreeam.leaf.command.subcommands;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.framework.qual.DefaultQualifier;
|
||||
import org.dreeam.leaf.command.LeafCommand;
|
||||
import org.dreeam.leaf.command.PermissionedLeafSubcommand;
|
||||
import org.dreeam.leaf.config.modules.async.SparklyPaperParallelWorldTicking;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.permissions.PermissionDefault;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static net.kyori.adventure.text.Component.text;
|
||||
import static net.kyori.adventure.text.format.NamedTextColor.*;
|
||||
|
||||
@DefaultQualifier(NonNull.class)
|
||||
public final class MSPTCommand extends PermissionedLeafSubcommand {
|
||||
|
||||
public static final String LITERAL_ARGUMENT = "mspt";
|
||||
public static final String PERM = LeafCommand.BASE_PERM + "." + LITERAL_ARGUMENT;
|
||||
private static final DecimalFormat DF = new DecimalFormat("########0.0");
|
||||
private static final Component SLASH = text("/");
|
||||
|
||||
public MSPTCommand() {
|
||||
super(PERM, PermissionDefault.TRUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
|
||||
// Check if parallel world ticking is enabled
|
||||
if (!SparklyPaperParallelWorldTicking.enabled) {
|
||||
sender.sendMessage(Component.text()
|
||||
.content("Per-world MSPT tracking is only available when parallel world ticking is enabled.")
|
||||
.color(RED)
|
||||
.build());
|
||||
sender.sendMessage(Component.text()
|
||||
.content("Please enable it in your Leaf configuration to use this command.")
|
||||
.color(GRAY)
|
||||
.build());
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if compact mode is requested
|
||||
boolean compactMode = args.length > 0 && args[0].equalsIgnoreCase("compact");
|
||||
|
||||
MinecraftServer server = MinecraftServer.getServer();
|
||||
|
||||
if (compactMode) {
|
||||
displayCompactStats(sender, server);
|
||||
} else {
|
||||
// Display header
|
||||
sender.sendMessage(Component.text()
|
||||
.content("━━━━━━━━━━━━━ ")
|
||||
.color(GOLD)
|
||||
.append(Component.text("MSPT Statistics").color(YELLOW))
|
||||
.append(Component.text(" ━━━━━━━━━━━━━").color(GOLD))
|
||||
.build());
|
||||
|
||||
// Overall server MSPT
|
||||
displayServerMSPT(sender, server);
|
||||
|
||||
// Add separator
|
||||
sender.sendMessage(Component.text(""));
|
||||
|
||||
// World-specific MSPT
|
||||
displayWorldMSPT(sender, server);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void displayCompactStats(CommandSender sender, MinecraftServer server) {
|
||||
// Get server stats (only 5s data with avg/min/max)
|
||||
List<Component> serverTimes = eval(server.tickTimes5s.getTimes());
|
||||
|
||||
// Display server stats in compact form
|
||||
sender.sendMessage(Component.text()
|
||||
.content("Server: ")
|
||||
.color(GOLD)
|
||||
.append(serverTimes.get(0)).append(SLASH).append(serverTimes.get(1)).append(SLASH).append(serverTimes.get(2))
|
||||
.build());
|
||||
|
||||
// Display world stats in compact form
|
||||
for (net.minecraft.server.level.ServerLevel serverLevel : server.getAllLevels()) {
|
||||
List<Component> worldTimes = eval(serverLevel.tickTimes5s.getTimes());
|
||||
|
||||
sender.sendMessage(Component.text()
|
||||
.content(serverLevel.getWorld().getName() + ": ")
|
||||
.color(GOLD)
|
||||
.append(worldTimes.get(0)).append(SLASH).append(worldTimes.get(1)).append(SLASH).append(worldTimes.get(2))
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
private void displayServerMSPT(CommandSender sender, MinecraftServer server) {
|
||||
List<Component> times = new ArrayList<>();
|
||||
times.addAll(eval(server.tickTimes5s.getTimes()));
|
||||
times.addAll(eval(server.tickTimes10s.getTimes()));
|
||||
times.addAll(eval(server.tickTimes60s.getTimes()));
|
||||
|
||||
sender.sendMessage(Component.text()
|
||||
.content("Server tick times ")
|
||||
.color(GOLD)
|
||||
.append(Component.text()
|
||||
.content("(avg/min/max)")
|
||||
.color(YELLOW)
|
||||
)
|
||||
.build());
|
||||
|
||||
sender.sendMessage(Component.text()
|
||||
.content(" 5s: ")
|
||||
.color(GOLD)
|
||||
.append(times.get(0)).append(SLASH).append(times.get(1)).append(SLASH).append(times.get(2))
|
||||
.build());
|
||||
|
||||
sender.sendMessage(Component.text()
|
||||
.content(" 10s: ")
|
||||
.color(GOLD)
|
||||
.append(times.get(3)).append(SLASH).append(times.get(4)).append(SLASH).append(times.get(5))
|
||||
.build());
|
||||
|
||||
sender.sendMessage(Component.text()
|
||||
.content(" 60s: ")
|
||||
.color(GOLD)
|
||||
.append(times.get(6)).append(SLASH).append(times.get(7)).append(SLASH).append(times.get(8))
|
||||
.build());
|
||||
}
|
||||
|
||||
private void displayWorldMSPT(CommandSender sender, MinecraftServer server) {
|
||||
sender.sendMessage(Component.text()
|
||||
.content("World-specific tick times ")
|
||||
.color(GOLD)
|
||||
.append(Component.text()
|
||||
.content("(avg/min/max)")
|
||||
.color(YELLOW)
|
||||
)
|
||||
.build());
|
||||
|
||||
for (net.minecraft.server.level.ServerLevel serverLevel : server.getAllLevels()) {
|
||||
List<Component> worldTimes = new ArrayList<>();
|
||||
worldTimes.addAll(eval(serverLevel.tickTimes5s.getTimes()));
|
||||
worldTimes.addAll(eval(serverLevel.tickTimes10s.getTimes()));
|
||||
worldTimes.addAll(eval(serverLevel.tickTimes60s.getTimes()));
|
||||
|
||||
// World name header
|
||||
sender.sendMessage(Component.text()
|
||||
.content("➤ ")
|
||||
.color(YELLOW)
|
||||
.append(Component.text(serverLevel.getWorld().getName()).color(GOLD))
|
||||
.build());
|
||||
|
||||
// Display time periods
|
||||
sender.sendMessage(Component.text()
|
||||
.content(" 5s: ")
|
||||
.color(GRAY)
|
||||
.append(worldTimes.get(0)).append(SLASH).append(worldTimes.get(1)).append(SLASH).append(worldTimes.get(2))
|
||||
.build());
|
||||
|
||||
sender.sendMessage(Component.text()
|
||||
.content(" 10s: ")
|
||||
.color(GRAY)
|
||||
.append(worldTimes.get(3)).append(SLASH).append(worldTimes.get(4)).append(SLASH).append(worldTimes.get(5))
|
||||
.build());
|
||||
|
||||
sender.sendMessage(Component.text()
|
||||
.content(" 60s: ")
|
||||
.color(GRAY)
|
||||
.append(worldTimes.get(6)).append(SLASH).append(worldTimes.get(7)).append(SLASH).append(worldTimes.get(8))
|
||||
.build());
|
||||
|
||||
boolean hasMoreWorlds = false;
|
||||
Iterable<net.minecraft.server.level.ServerLevel> levels = server.getAllLevels();
|
||||
for (net.minecraft.server.level.ServerLevel level : levels) {
|
||||
if (level != serverLevel) {
|
||||
hasMoreWorlds = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasMoreWorlds) {
|
||||
sender.sendMessage(Component.text(""));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Component> eval(long[] times) {
|
||||
long min = Integer.MAX_VALUE;
|
||||
long max = 0L;
|
||||
long total = 0L;
|
||||
int count = 0;
|
||||
|
||||
for (long value : times) {
|
||||
if (value > 0L) {
|
||||
count++;
|
||||
if (value < min) min = value;
|
||||
if (value > max) max = value;
|
||||
total += value;
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0) {
|
||||
// No data available yet
|
||||
return Arrays.asList(
|
||||
text("N/A", GRAY),
|
||||
text("N/A", GRAY),
|
||||
text("N/A", GRAY)
|
||||
);
|
||||
}
|
||||
|
||||
double avgD = ((double) total / (double) count) * 1.0E-6D;
|
||||
double minD = ((double) min) * 1.0E-6D;
|
||||
double maxD = ((double) max) * 1.0E-6D;
|
||||
|
||||
return Arrays.asList(getColoredValue(avgD), getColoredValue(minD), getColoredValue(maxD));
|
||||
}
|
||||
|
||||
private static Component getColoredValue(double value) {
|
||||
return text(DF.format(value) + "ms",
|
||||
value >= 50 ? RED :
|
||||
value >= 40 ? YELLOW :
|
||||
value >= 30 ? GOLD :
|
||||
value >= 20 ? GREEN :
|
||||
AQUA);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> tabComplete(final CommandSender sender, final String subCommand, final String[] args) {
|
||||
if (!SparklyPaperParallelWorldTicking.enabled) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
if (args.length == 1) {
|
||||
return Collections.singletonList("compact");
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package org.dreeam.leaf.command.subcommands;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.framework.qual.DefaultQualifier;
|
||||
import org.dreeam.leaf.command.LeafCommand;
|
||||
import org.dreeam.leaf.command.PermissionedLeafSubcommand;
|
||||
import org.dreeam.leaf.config.LeafConfig;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.permissions.PermissionDefault;
|
||||
|
||||
@DefaultQualifier(NonNull.class)
|
||||
public final class ReloadCommand extends PermissionedLeafSubcommand {
|
||||
|
||||
public final static String LITERAL_ARGUMENT = "reload";
|
||||
public static final String PERM = LeafCommand.BASE_PERM + "." + LITERAL_ARGUMENT;
|
||||
|
||||
public ReloadCommand() {
|
||||
super(PERM, PermissionDefault.OP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
|
||||
this.doReload(sender);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void doReload(final CommandSender sender) {
|
||||
Command.broadcastCommandMessage(sender, Component.text("Reloading Leaf config...", NamedTextColor.GREEN));
|
||||
|
||||
LeafConfig.reloadAsync(sender);
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package org.dreeam.leaf.command.subcommands;
|
||||
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.checkerframework.framework.qual.DefaultQualifier;
|
||||
import org.dreeam.leaf.command.LeafCommand;
|
||||
import org.dreeam.leaf.command.PermissionedLeafSubcommand;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.permissions.PermissionDefault;
|
||||
|
||||
@DefaultQualifier(NonNull.class)
|
||||
public final class VersionCommand extends PermissionedLeafSubcommand {
|
||||
|
||||
public final static String LITERAL_ARGUMENT = "version";
|
||||
public static final String PERM = LeafCommand.BASE_PERM + "." + LITERAL_ARGUMENT;
|
||||
|
||||
public VersionCommand() {
|
||||
super(PERM, PermissionDefault.TRUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
|
||||
final @Nullable Command ver = MinecraftServer.getServer().server.getCommandMap().getCommand("version");
|
||||
|
||||
if (ver != null) {
|
||||
ver.execute(sender, LeafCommand.COMMAND_LABEL, me.titaniumtown.ArrayConstants.emptyStringArray); // Gale - JettPack - reduce array allocations
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean testPermission(CommandSender sender) {
|
||||
return super.testPermission(sender) && sender.hasPermission("bukkit.command.version");
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package org.dreeam.leaf.config;
|
||||
|
||||
import org.dreeam.leaf.config.annotations.Experimental;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public abstract class ConfigModules extends LeafConfig {
|
||||
|
||||
private static final Set<ConfigModules> MODULES = new HashSet<>();
|
||||
|
||||
public LeafGlobalConfig config;
|
||||
|
||||
public ConfigModules() {
|
||||
this.config = LeafConfig.config();
|
||||
}
|
||||
|
||||
public static void initModules() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
|
||||
List<Field> enabledExperimentalModules = new ArrayList<>();
|
||||
|
||||
for (Class<?> clazz : LeafConfig.getClasses(LeafConfig.I_CONFIG_PKG)) {
|
||||
ConfigModules module = (ConfigModules) clazz.getConstructor().newInstance();
|
||||
module.onLoaded();
|
||||
|
||||
MODULES.add(module);
|
||||
for (Field field : getAnnotatedStaticFields(clazz, Experimental.class)) {
|
||||
if (!(field.get(null) instanceof Boolean enabled)) continue;
|
||||
if (enabled) {
|
||||
enabledExperimentalModules.add(field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!enabledExperimentalModules.isEmpty()) {
|
||||
LeafConfig.LOGGER.warn("You have following experimental module(s) enabled: {}, please proceed with caution!", enabledExperimentalModules.stream().map(f -> f.getDeclaringClass().getSimpleName() + "." + f.getName()).toList());
|
||||
}
|
||||
}
|
||||
|
||||
public static void loadAfterBootstrap() {
|
||||
for (ConfigModules module : MODULES) {
|
||||
module.onPostLoaded();
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Field> getAnnotatedStaticFields(Class<?> clazz, Class<? extends Annotation> annotation) {
|
||||
List<Field> fields = new ArrayList<>();
|
||||
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
if (field.isAnnotationPresent(annotation) && Modifier.isStatic(field.getModifiers())) {
|
||||
field.setAccessible(true);
|
||||
fields.add(field);
|
||||
}
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
public static void clearModules() {
|
||||
MODULES.clear();
|
||||
}
|
||||
|
||||
public abstract void onLoaded();
|
||||
|
||||
public void onPostLoaded() {
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package org.dreeam.leaf.config;
|
||||
|
||||
public enum EnumConfigCategory {
|
||||
ASYNC("async"),
|
||||
PERF("performance"),
|
||||
FIXES("fixes"),
|
||||
GAMEPLAY("gameplay-mechanisms"),
|
||||
NETWORK("network"),
|
||||
MISC("misc");
|
||||
|
||||
private final String baseKeyName;
|
||||
private static final EnumConfigCategory[] VALUES = EnumConfigCategory.values();
|
||||
|
||||
EnumConfigCategory(String baseKeyName) {
|
||||
this.baseKeyName = baseKeyName;
|
||||
}
|
||||
|
||||
public String getBaseKeyName() {
|
||||
return this.baseKeyName;
|
||||
}
|
||||
|
||||
public static EnumConfigCategory[] getCategoryValues() {
|
||||
return VALUES;
|
||||
}
|
||||
}
|
||||
@@ -1,292 +0,0 @@
|
||||
package org.dreeam.leaf.config;
|
||||
|
||||
import io.papermc.paper.configuration.GlobalConfiguration;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.minecraft.Util;
|
||||
import org.dreeam.leaf.config.modules.misc.SentryDSN;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.JarURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.Enumeration;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
/*
|
||||
* Yoinked from: https://github.com/xGinko/AnarchyExploitFixes/ & https://github.com/LuminolMC/Luminol
|
||||
* @author: @xGinko & @MrHua269
|
||||
*/
|
||||
public class LeafConfig {
|
||||
|
||||
public static final Logger LOGGER = LogManager.getLogger(LeafConfig.class.getSimpleName());
|
||||
protected static final File I_CONFIG_FOLDER = new File("config");
|
||||
protected static final String I_CONFIG_PKG = "org.dreeam.leaf.config.modules";
|
||||
protected static final String I_GLOBAL_CONFIG_FILE = "leaf-global.yml";
|
||||
protected static final String I_LEVEL_CONFIG_FILE = "leaf-world-defaults.yml"; // Leaf TODO - Per level config
|
||||
|
||||
private static LeafGlobalConfig leafGlobalConfig;
|
||||
|
||||
//private static int preMajorVer;
|
||||
private static int preMinorVer;
|
||||
//private static int currMajorVer;
|
||||
private static int currMinorVer;
|
||||
|
||||
/* Load & Reload */
|
||||
|
||||
public static @NotNull CompletableFuture<Void> reloadAsync(CommandSender sender) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
long begin = System.nanoTime();
|
||||
|
||||
ConfigModules.clearModules();
|
||||
loadConfig(false);
|
||||
ConfigModules.loadAfterBootstrap();
|
||||
|
||||
final String success = String.format("Successfully reloaded config in %sms.", (System.nanoTime() - begin) / 1_000_000);
|
||||
Command.broadcastCommandMessage(sender, Component.text(success, NamedTextColor.GREEN));
|
||||
} catch (Exception e) {
|
||||
Command.broadcastCommandMessage(sender, Component.text("Failed to reload config. See error in console!", NamedTextColor.RED));
|
||||
LOGGER.error(e);
|
||||
}
|
||||
}, Util.ioPool());
|
||||
}
|
||||
|
||||
public static void loadConfig() {
|
||||
try {
|
||||
long begin = System.nanoTime();
|
||||
LOGGER.info("Loading config...");
|
||||
|
||||
purgeOutdated();
|
||||
loadConfig(true);
|
||||
|
||||
LOGGER.info("Successfully loaded config in {}ms.", (System.nanoTime() - begin) / 1_000_000);
|
||||
} catch (Exception e) {
|
||||
LeafConfig.LOGGER.error("Failed to load config modules!", e);
|
||||
}
|
||||
}
|
||||
|
||||
/* Load Global Config */
|
||||
|
||||
private static void loadConfig(boolean init) throws Exception {
|
||||
// Create config folder
|
||||
createDirectory(LeafConfig.I_CONFIG_FOLDER);
|
||||
|
||||
leafGlobalConfig = new LeafGlobalConfig(init);
|
||||
|
||||
// Load config modules
|
||||
ConfigModules.initModules();
|
||||
|
||||
// Save config to disk
|
||||
leafGlobalConfig.saveConfig();
|
||||
}
|
||||
|
||||
public static LeafGlobalConfig config() {
|
||||
return leafGlobalConfig;
|
||||
}
|
||||
|
||||
/* Create config folder */
|
||||
|
||||
protected static void createDirectory(File dir) throws IOException {
|
||||
try {
|
||||
Files.createDirectories(dir.toPath());
|
||||
} catch (FileAlreadyExistsException e) { // Thrown if dir exists but is not a directory
|
||||
if (dir.delete()) createDirectory(dir);
|
||||
}
|
||||
}
|
||||
|
||||
/* Scan classes under package */
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO
|
||||
public static void loadConfigVersion(String preVer, String currVer) {
|
||||
int currMinor;
|
||||
int preMinor;
|
||||
|
||||
// First time user
|
||||
if (preVer == null) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/* Register Spark profiler extra server configurations */
|
||||
|
||||
private static List<String> buildSparkExtraConfigs() {
|
||||
List<String> extraConfigs = new ArrayList<>(Arrays.asList(
|
||||
"config/leaf-global.yml",
|
||||
"config/gale-global.yml",
|
||||
"config/gale-world-defaults.yml"
|
||||
));
|
||||
|
||||
for (World world : Bukkit.getWorlds()) {
|
||||
extraConfigs.add(world.getWorldFolder().getName() + "/gale-world.yml"); // Gale world config
|
||||
}
|
||||
|
||||
return extraConfigs;
|
||||
}
|
||||
|
||||
private static String[] buildSparkHiddenPaths() {
|
||||
return new String[]{
|
||||
SentryDSN.sentryDsnConfigPath // Hide Sentry DSN key
|
||||
};
|
||||
}
|
||||
|
||||
public static void regSparkExtraConfig() {
|
||||
if (GlobalConfiguration.get().spark.enabled || Bukkit.getServer().getPluginManager().getPlugin("spark") != null) {
|
||||
String extraConfigs = String.join(",", buildSparkExtraConfigs());
|
||||
String hiddenPaths = String.join(",", buildSparkHiddenPaths());
|
||||
|
||||
System.setProperty("spark.serverconfigs.extra", extraConfigs);
|
||||
System.setProperty("spark.serverconfigs.hiddenpaths", hiddenPaths);
|
||||
}
|
||||
}
|
||||
|
||||
/* Purge and backup old Leaf config & Pufferfish config */
|
||||
|
||||
private static void purgeOutdated() {
|
||||
boolean foundLegacy = false;
|
||||
String pufferfishConfig = "pufferfish.yml";
|
||||
String leafConfigV1 = "leaf.yml";
|
||||
String leafConfigV2 = "leaf_config";
|
||||
|
||||
Date date = new Date();
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyMMddhhmmss");
|
||||
String backupDir = "config/backup" + dateFormat.format(date) + "/";
|
||||
|
||||
File pufferfishConfigFile = new File(pufferfishConfig);
|
||||
File leafConfigV1File = new File(leafConfigV1);
|
||||
File leafConfigV2File = new File(leafConfigV2);
|
||||
File backupDirFile = new File(backupDir);
|
||||
|
||||
try {
|
||||
if (pufferfishConfigFile.exists() && pufferfishConfigFile.isFile()) {
|
||||
createDirectory(backupDirFile);
|
||||
Files.move(pufferfishConfigFile.toPath(), Path.of(backupDir + pufferfishConfig), StandardCopyOption.REPLACE_EXISTING);
|
||||
foundLegacy = true;
|
||||
}
|
||||
if (leafConfigV1File.exists() && leafConfigV1File.isFile()) {
|
||||
createDirectory(backupDirFile);
|
||||
Files.move(leafConfigV1File.toPath(), Path.of(backupDir + leafConfigV1), StandardCopyOption.REPLACE_EXISTING);
|
||||
foundLegacy = true;
|
||||
}
|
||||
if (leafConfigV2File.exists() && leafConfigV2File.isDirectory()) {
|
||||
createDirectory(backupDirFile);
|
||||
Files.move(leafConfigV2File.toPath(), Path.of(backupDir + leafConfigV2), StandardCopyOption.REPLACE_EXISTING);
|
||||
foundLegacy = true;
|
||||
}
|
||||
|
||||
if (foundLegacy) {
|
||||
LOGGER.warn("Found legacy Leaf config files, move to backup directory: {}", backupDir);
|
||||
LOGGER.warn("New Leaf config located at config/ folder, You need to transfer config to the new one manually and restart the server!");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Failed to purge old configs.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
package org.dreeam.leaf.config;
|
||||
|
||||
import io.github.thatsmusic99.configurationmaster.api.ConfigFile;
|
||||
import io.github.thatsmusic99.configurationmaster.api.ConfigSection;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
public class LeafGlobalConfig {
|
||||
|
||||
private static final String CURRENT_VERSION = "3.0";
|
||||
private static final String CURRENT_REGION = Locale.getDefault().getCountry().toUpperCase(Locale.ROOT); // It will be in uppercase by default, just make sure
|
||||
private static final boolean isCN = CURRENT_REGION.equals("CN");
|
||||
|
||||
private static ConfigFile configFile;
|
||||
|
||||
public LeafGlobalConfig(boolean init) throws Exception {
|
||||
configFile = ConfigFile.loadConfig(new File(LeafConfig.I_CONFIG_FOLDER, LeafConfig.I_GLOBAL_CONFIG_FILE));
|
||||
|
||||
LeafConfig.loadConfigVersion(getString("config-version"), CURRENT_VERSION);
|
||||
configFile.set("config-version", CURRENT_VERSION);
|
||||
|
||||
configFile.addComments("config-version", pickStringRegionBased("""
|
||||
Leaf Config
|
||||
GitHub Repo: https://github.com/Winds-Studio/Leaf
|
||||
Discord: https://discord.com/invite/gfgAwdSEuM""",
|
||||
"""
|
||||
Leaf Config
|
||||
GitHub Repo: https://github.com/Winds-Studio/Leaf
|
||||
QQ Group: 619278377"""));
|
||||
|
||||
// Pre-structure to force order
|
||||
structureConfig();
|
||||
}
|
||||
|
||||
protected void structureConfig() {
|
||||
for (EnumConfigCategory configCate : EnumConfigCategory.getCategoryValues()) {
|
||||
createTitledSection(configCate.name(), configCate.getBaseKeyName());
|
||||
}
|
||||
}
|
||||
|
||||
public void saveConfig() throws Exception {
|
||||
configFile.save();
|
||||
}
|
||||
|
||||
// Config Utilities
|
||||
|
||||
/* getAndSet */
|
||||
|
||||
public void createTitledSection(String title, String path) {
|
||||
configFile.addSection(title);
|
||||
configFile.addDefault(path, null);
|
||||
}
|
||||
|
||||
public boolean getBoolean(String path, boolean def, String comment) {
|
||||
configFile.addDefault(path, def, comment);
|
||||
return configFile.getBoolean(path, def);
|
||||
}
|
||||
|
||||
public boolean getBoolean(String path, boolean def) {
|
||||
configFile.addDefault(path, def);
|
||||
return configFile.getBoolean(path, def);
|
||||
}
|
||||
|
||||
public String getString(String path, String def, String comment) {
|
||||
configFile.addDefault(path, def, comment);
|
||||
return configFile.getString(path, def);
|
||||
}
|
||||
|
||||
public String getString(String path, String def) {
|
||||
configFile.addDefault(path, def);
|
||||
return configFile.getString(path, def);
|
||||
}
|
||||
|
||||
public double getDouble(String path, double def, String comment) {
|
||||
configFile.addDefault(path, def, comment);
|
||||
return configFile.getDouble(path, def);
|
||||
}
|
||||
|
||||
public double getDouble(String path, double def) {
|
||||
configFile.addDefault(path, def);
|
||||
return configFile.getDouble(path, def);
|
||||
}
|
||||
|
||||
public int getInt(String path, int def, String comment) {
|
||||
configFile.addDefault(path, def, comment);
|
||||
return configFile.getInteger(path, def);
|
||||
}
|
||||
|
||||
public int getInt(String path, int def) {
|
||||
configFile.addDefault(path, def);
|
||||
return configFile.getInteger(path, def);
|
||||
}
|
||||
|
||||
public long getLong(String path, long def, String comment) {
|
||||
configFile.addDefault(path, def, comment);
|
||||
return configFile.getLong(path, def);
|
||||
}
|
||||
|
||||
public long getLong(String path, long def) {
|
||||
configFile.addDefault(path, def);
|
||||
return configFile.getLong(path, def);
|
||||
}
|
||||
|
||||
public List<String> getList(String path, List<String> def, String comment) {
|
||||
configFile.addDefault(path, def, comment);
|
||||
return configFile.getStringList(path);
|
||||
}
|
||||
|
||||
public List<String> getList(String path, List<String> def) {
|
||||
configFile.addDefault(path, def);
|
||||
return configFile.getStringList(path);
|
||||
}
|
||||
|
||||
public ConfigSection getConfigSection(String path, Map<String, Object> defaultKeyValue, String comment) {
|
||||
configFile.addDefault(path, null, comment);
|
||||
configFile.makeSectionLenient(path);
|
||||
defaultKeyValue.forEach((string, object) -> configFile.addExample(path + "." + string, object));
|
||||
return configFile.getConfigSection(path);
|
||||
}
|
||||
|
||||
public ConfigSection getConfigSection(String path, Map<String, Object> defaultKeyValue) {
|
||||
configFile.addDefault(path, null);
|
||||
configFile.makeSectionLenient(path);
|
||||
defaultKeyValue.forEach((string, object) -> configFile.addExample(path + "." + string, object));
|
||||
return configFile.getConfigSection(path);
|
||||
}
|
||||
|
||||
/* get */
|
||||
|
||||
public Boolean getBoolean(String path) {
|
||||
String value = configFile.getString(path, null);
|
||||
return value == null ? null : Boolean.parseBoolean(value);
|
||||
}
|
||||
|
||||
public String getString(String path) {
|
||||
return configFile.getString(path, null);
|
||||
}
|
||||
|
||||
public Double getDouble(String path) {
|
||||
String value = configFile.getString(path, null);
|
||||
return value == null ? null : Double.parseDouble(value); // TODO: Need to check whether need to handle NFE correctly
|
||||
}
|
||||
|
||||
public Integer getInt(String path) {
|
||||
String value = configFile.getString(path, null);
|
||||
return value == null ? null : Integer.parseInt(value); // TODO: Need to check whether need to handle NFE correctly
|
||||
}
|
||||
|
||||
public Long getLong(String path) {
|
||||
String value = configFile.getString(path, null);
|
||||
return value == null ? null : Long.parseLong(value); // TODO: Need to check whether need to handle NFE correctly
|
||||
}
|
||||
|
||||
public List<String> getList(String path) {
|
||||
return configFile.getList(path, null);
|
||||
}
|
||||
|
||||
// TODO, check
|
||||
public ConfigSection getConfigSection(String path) {
|
||||
configFile.addDefault(path, null);
|
||||
configFile.makeSectionLenient(path);
|
||||
//defaultKeyValue.forEach((string, object) -> configFile.addExample(path + "." + string, object));
|
||||
return configFile.getConfigSection(path);
|
||||
}
|
||||
|
||||
public void addComment(String path, String comment) {
|
||||
configFile.addComment(path, comment);
|
||||
}
|
||||
|
||||
public void addCommentIfCN(String path, String comment) {
|
||||
if (isCN) {
|
||||
configFile.addComment(path, comment);
|
||||
}
|
||||
}
|
||||
|
||||
public void addCommentIfNonCN(String path, String comment) {
|
||||
if (!isCN) {
|
||||
configFile.addComment(path, comment);
|
||||
}
|
||||
}
|
||||
|
||||
public void addCommentRegionBased(String path, String en, String cn) {
|
||||
configFile.addComment(path, isCN ? cn : en);
|
||||
}
|
||||
|
||||
public String pickStringRegionBased(String en, String cn) {
|
||||
return isCN ? cn : en;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package org.dreeam.leaf.config.annotations;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface DoNotLoad {
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package org.dreeam.leaf.config.annotations;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* Indicates that a feature is experimental and may be removed or changed in the future.
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(value = {ElementType.FIELD})
|
||||
public @interface Experimental {
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package org.dreeam.leaf.config.annotations;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface HotReloadUnsupported {
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.async;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
import org.dreeam.leaf.config.annotations.Experimental;
|
||||
|
||||
public class AsyncBlockFinding extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-block-finding";
|
||||
}
|
||||
|
||||
@Experimental
|
||||
public static boolean enabled = false;
|
||||
|
||||
public static boolean asyncBlockFindingInitialized;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
config.addCommentRegionBased(getBasePath(), """
|
||||
**Experimental feature**
|
||||
This moves the expensive search calculations to a background thread while
|
||||
keeping the actual block validation on the main thread.""",
|
||||
"""
|
||||
这会将昂贵的搜索计算移至后台线程, 同时在主线程上保持实际的方块验证.""");
|
||||
|
||||
if (!asyncBlockFindingInitialized) {
|
||||
asyncBlockFindingInitialized = true;
|
||||
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.async;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
import org.dreeam.leaf.config.annotations.Experimental;
|
||||
|
||||
public class AsyncChunkSend extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-chunk-send";
|
||||
}
|
||||
|
||||
public static boolean enabled = false;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
config.addCommentRegionBased(getBasePath(),
|
||||
"""
|
||||
Makes chunk packet preparation and sending asynchronous to improve server performance.
|
||||
This can significantly reduce main thread load when many players are loading chunks.""",
|
||||
"""
|
||||
使区块数据包准备和发送异步化以提高服务器性能.
|
||||
当许多玩家同时加载区块时, 这可以显著减少主线程负载.""");
|
||||
|
||||
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.async;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
import org.dreeam.leaf.config.LeafConfig;
|
||||
|
||||
public class AsyncLocator extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-locator";
|
||||
}
|
||||
|
||||
public static boolean enabled = false;
|
||||
public static int asyncLocatorThreads = 0;
|
||||
public static int asyncLocatorKeepalive = 60;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
config.addCommentRegionBased(getBasePath(), """
|
||||
Whether or not asynchronous locator should be enabled.
|
||||
This offloads structure locating to other threads.
|
||||
Only for locate command, dolphin treasure finding and eye of ender currently.""",
|
||||
"""
|
||||
是否启用异步结构搜索.
|
||||
目前可用于 /locate 指令, 海豚寻宝和末影之眼.""");
|
||||
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
|
||||
asyncLocatorThreads = config.getInt(getBasePath() + ".threads", asyncLocatorThreads);
|
||||
asyncLocatorKeepalive = config.getInt(getBasePath() + ".keepalive", asyncLocatorKeepalive);
|
||||
|
||||
if (asyncLocatorThreads <= 0)
|
||||
asyncLocatorThreads = 1;
|
||||
if (!enabled)
|
||||
asyncLocatorThreads = 0;
|
||||
else
|
||||
LeafConfig.LOGGER.info("Using {} threads for Async Locator", asyncLocatorThreads);
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.async;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class AsyncMobSpawning extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-mob-spawning";
|
||||
}
|
||||
|
||||
public static boolean enabled = true;
|
||||
public static boolean asyncMobSpawningInitialized;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
config.addCommentRegionBased(getBasePath(), """
|
||||
Whether or not asynchronous mob spawning should be enabled.
|
||||
On servers with many entities, this can improve performance by up to 15%. You must have
|
||||
paper's per-player-mob-spawns setting set to true for this to work.
|
||||
One quick note - this does not actually spawn mobs async (that would be very unsafe).
|
||||
This just offloads some expensive calculations that are required for mob spawning.""",
|
||||
"""
|
||||
是否异步化生物生成.
|
||||
在实体较多的服务器上, 异步生成可最高带来15%的性能提升.
|
||||
须在Paper配置文件中打开 per-player-mob-spawns 才能生效.""");
|
||||
|
||||
// This prevents us from changing the value during a reload.
|
||||
if (!asyncMobSpawningInitialized) {
|
||||
asyncMobSpawningInitialized = true;
|
||||
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.async;
|
||||
|
||||
import org.dreeam.leaf.async.path.PathfindTaskRejectPolicy;
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
import org.dreeam.leaf.config.LeafConfig;
|
||||
|
||||
public class AsyncPathfinding extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-pathfinding";
|
||||
}
|
||||
|
||||
public static boolean enabled = false;
|
||||
public static int asyncPathfindingMaxThreads = 0;
|
||||
public static int asyncPathfindingKeepalive = 60;
|
||||
public static int asyncPathfindingQueueSize = 0;
|
||||
public static PathfindTaskRejectPolicy asyncPathfindingRejectPolicy = PathfindTaskRejectPolicy.FLUSH_ALL;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
final int availableProcessors = Runtime.getRuntime().availableProcessors();
|
||||
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
|
||||
asyncPathfindingMaxThreads = config.getInt(getBasePath() + ".max-threads", asyncPathfindingMaxThreads);
|
||||
asyncPathfindingKeepalive = config.getInt(getBasePath() + ".keepalive", asyncPathfindingKeepalive);
|
||||
asyncPathfindingQueueSize = config.getInt(getBasePath() + ".queue-size", asyncPathfindingQueueSize);
|
||||
|
||||
if (asyncPathfindingMaxThreads < 0)
|
||||
asyncPathfindingMaxThreads = Math.max(availableProcessors + asyncPathfindingMaxThreads, 1);
|
||||
else if (asyncPathfindingMaxThreads == 0)
|
||||
asyncPathfindingMaxThreads = Math.max(availableProcessors / 4, 1);
|
||||
if (!enabled)
|
||||
asyncPathfindingMaxThreads = 0;
|
||||
else
|
||||
LeafConfig.LOGGER.info("Using {} threads for Async Pathfinding", asyncPathfindingMaxThreads);
|
||||
|
||||
if (asyncPathfindingQueueSize <= 0)
|
||||
asyncPathfindingQueueSize = asyncPathfindingMaxThreads * 256;
|
||||
|
||||
asyncPathfindingRejectPolicy = PathfindTaskRejectPolicy.fromString(config.getString(getBasePath() + ".reject-policy", availableProcessors >= 12 && asyncPathfindingQueueSize < 512 ? PathfindTaskRejectPolicy.FLUSH_ALL.toString() : PathfindTaskRejectPolicy.CALLER_RUNS.toString(), config.pickStringRegionBased(
|
||||
"""
|
||||
The policy to use when the queue is full and a new task is submitted.
|
||||
FLUSH_ALL: All pending tasks will be run on server thread.
|
||||
CALLER_RUNS: Newly submitted task will be run on server thread.""",
|
||||
"""
|
||||
当队列满时, 新提交的任务将使用以下策略处理.
|
||||
FLUSH_ALL: 所有等待中的任务都将在主线程上运行.
|
||||
CALLER_RUNS: 新提交的任务将在主线程上运行."""
|
||||
)));
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.async;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
import org.dreeam.leaf.config.annotations.Experimental;
|
||||
|
||||
public class AsyncPlayerDataSave extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-playerdata-save";
|
||||
}
|
||||
|
||||
@Experimental
|
||||
public static boolean enabled = false;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
config.addCommentRegionBased(getBasePath(),
|
||||
"""
|
||||
**Experimental feature, may have data lost in some circumstances!**
|
||||
Make PlayerData saving asynchronously.""",
|
||||
"""
|
||||
**实验性功能, 在部分场景下可能丢失玩家数据!**
|
||||
异步保存玩家数据.""");
|
||||
|
||||
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.async;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
import org.dreeam.leaf.config.LeafConfig;
|
||||
|
||||
public class MultithreadedTracker extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.ASYNC.getBaseKeyName() + ".async-entity-tracker";
|
||||
}
|
||||
|
||||
public static boolean enabled = false;
|
||||
public static boolean compatModeEnabled = false;
|
||||
public static int asyncEntityTrackerMaxThreads = 0;
|
||||
public static int asyncEntityTrackerKeepalive = 60;
|
||||
public static int asyncEntityTrackerQueueSize = 0;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
config.addCommentRegionBased(getBasePath(), """
|
||||
Make entity tracking saving asynchronously, can improve performance significantly,
|
||||
especially in some massive entities in small area situations.""",
|
||||
"""
|
||||
异步实体跟踪,
|
||||
在实体数量多且密集的情况下效果明显.""");
|
||||
|
||||
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
|
||||
compatModeEnabled = config.getBoolean(getBasePath() + ".compat-mode", compatModeEnabled, config.pickStringRegionBased("""
|
||||
Enable compat mode ONLY if Citizens or NPC plugins using real entity has installed,
|
||||
Compat mode fixed visible issue with player type NPCs of Citizens,
|
||||
But still recommend to use packet based / virtual entity NPC plugin, e.g. ZNPC Plus, Adyeshach, Fancy NPC or else.""",
|
||||
"""
|
||||
是否启用兼容模式,
|
||||
如果你的服务器安装了 Citizens 或其他类似非发包 NPC 插件, 请开启此项."""));
|
||||
asyncEntityTrackerMaxThreads = config.getInt(getBasePath() + ".max-threads", asyncEntityTrackerMaxThreads);
|
||||
asyncEntityTrackerKeepalive = config.getInt(getBasePath() + ".keepalive", asyncEntityTrackerKeepalive);
|
||||
asyncEntityTrackerQueueSize = config.getInt(getBasePath() + ".queue-size", asyncEntityTrackerQueueSize);
|
||||
|
||||
if (asyncEntityTrackerMaxThreads < 0)
|
||||
asyncEntityTrackerMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() + asyncEntityTrackerMaxThreads, 1);
|
||||
else if (asyncEntityTrackerMaxThreads == 0)
|
||||
asyncEntityTrackerMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() / 4, 1);
|
||||
|
||||
if (!enabled)
|
||||
asyncEntityTrackerMaxThreads = 0;
|
||||
else
|
||||
LeafConfig.LOGGER.info("Using {} threads for Async Entity Tracker", asyncEntityTrackerMaxThreads);
|
||||
|
||||
if (asyncEntityTrackerQueueSize <= 0)
|
||||
asyncEntityTrackerQueueSize = asyncEntityTrackerMaxThreads * 384;
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.async;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
import org.dreeam.leaf.config.annotations.Experimental;
|
||||
|
||||
public class SparklyPaperParallelWorldTicking extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.ASYNC.getBaseKeyName() + ".parallel-world-tracking";
|
||||
} // TODO: Correct config key when stable
|
||||
|
||||
@Experimental
|
||||
public static boolean enabled = false;
|
||||
public static int threads = 8;
|
||||
public static boolean logContainerCreationStacktraces = false;
|
||||
public static boolean disableHardThrow = false;
|
||||
public static boolean runAsyncTasksSync = false;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
config.addCommentRegionBased(getBasePath(),
|
||||
"""
|
||||
**Experimental feature**
|
||||
Enables parallel world ticking to improve performance on multi-core systems..""",
|
||||
"""
|
||||
**实验性功能**
|
||||
启用并行世界处理以提高多核系统的性能.""");
|
||||
|
||||
|
||||
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
|
||||
threads = config.getInt(getBasePath() + ".threads", threads);
|
||||
threads = enabled ? threads : 0;
|
||||
logContainerCreationStacktraces = config.getBoolean(getBasePath() + ".log-container-creation-stacktraces", logContainerCreationStacktraces);
|
||||
logContainerCreationStacktraces = enabled && logContainerCreationStacktraces;
|
||||
disableHardThrow = config.getBoolean(getBasePath() + ".disable-hard-throw", disableHardThrow);
|
||||
disableHardThrow = enabled && disableHardThrow;
|
||||
runAsyncTasksSync = config.getBoolean(getBasePath() + ".run-async-tasks-sync", runAsyncTasksSync);
|
||||
runAsyncTasksSync = enabled && runAsyncTasksSync;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.fixes;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class DontPlacePlayerIfFull extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.FIXES.getBaseKeyName();
|
||||
}
|
||||
|
||||
public static boolean enabled = false;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
enabled = config.getBoolean(getBasePath() + ".dont-place-player-if-server-full", enabled, config.pickStringRegionBased("""
|
||||
Don't let player join server if the server is full.
|
||||
If enable this, you should use 'purpur.joinfullserver' permission instead of
|
||||
PlayerLoginEvent#allow to let player join full server.""",
|
||||
"""
|
||||
服务器已满时禁止玩家加入.
|
||||
开启后需使用权限 'purpur.joinfullserver' 而不是
|
||||
PlayerLoginEvent#allow 让玩家进入已满的服务器."""));
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.gameplay;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class AfkCommand extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.GAMEPLAY.getBaseKeyName() + ".afk-command";
|
||||
}
|
||||
|
||||
public static boolean enabled = false;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
enabled = config.getBoolean(getBasePath() + ".enabled", enabled, config.pickStringRegionBased("""
|
||||
The AFK command based on Minecraft built-in idle-timeout mechanism
|
||||
Rest of AFK settings are in the Purpur config""",
|
||||
"""
|
||||
基于原版 idle-timeout 系统的 AFK 指令
|
||||
剩余配置项在 Purpur 配置里"""));
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.gameplay;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class ConfigurableMaxUseItemDistance extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.GAMEPLAY.getBaseKeyName() + ".player";
|
||||
}
|
||||
|
||||
public static double maxUseItemDistance = 1.0000001;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
maxUseItemDistance = config.getDouble(getBasePath() + ".max-use-item-distance", maxUseItemDistance, config.pickStringRegionBased("""
|
||||
The max distance of UseItem for players.
|
||||
Set to -1 to disable max-distance-check.
|
||||
NOTE: if set to -1 to disable the check,
|
||||
players are able to use some packet modules of hack clients,
|
||||
and NoCom Exploit!!""",
|
||||
"""
|
||||
玩家 UseItem 的最大距离.
|
||||
设置为 -1 来禁用最大距离检测.
|
||||
注意: 禁用此项后,
|
||||
玩家可以使用作弊客户端的部分发包模块和 NoCom 漏洞!!"""));
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.gameplay;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class ConfigurableTripWireDupe extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.GAMEPLAY.getBaseKeyName();
|
||||
}
|
||||
|
||||
public static boolean enabled = false;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
enabled = config.getBoolean(getBasePath() + ".allow-tripwire-dupe", enabled);
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.gameplay;
|
||||
|
||||
import net.minecraft.core.component.DataComponentType;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
import org.dreeam.leaf.config.LeafConfig;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* HideItemComponent
|
||||
*
|
||||
* @author TheFloodDragon
|
||||
* @since 2025/2/4 18:30
|
||||
*/
|
||||
public class HideItemComponent extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.GAMEPLAY.getBaseKeyName() + ".hide-item-component";
|
||||
}
|
||||
|
||||
public static boolean enabled = false;
|
||||
public static List<String> hiddenTypeStrings = new ArrayList<>();
|
||||
public static List<DataComponentType<?>> hiddenTypes = List.of();
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
config.addCommentRegionBased(getBasePath(), """
|
||||
Controls whether specified component information would be sent to clients.
|
||||
It may break resource packs and mods that rely on the information.
|
||||
Also, it can avoid some frequent client animations.
|
||||
Attention: This is not same as Paper's item-obfuscation, we only hide specified component information from player's inventory.""",
|
||||
"""
|
||||
控制哪些物品组件信息会被发送至客户端.
|
||||
可能会导致依赖物品组件的资源包/模组无法正常工作.
|
||||
可以避免一些客户端动画效果.
|
||||
注意: 此项与 Paper 的 item-obfuscation 不同, 我们只从玩家背包中隐藏物品指定的组件信息.""");
|
||||
hiddenTypeStrings = config.getList(getBasePath() + ".hidden-types", new ArrayList<>(), config.pickStringRegionBased("""
|
||||
Which type of components will be hidden from clients.
|
||||
It needs a component type list, incorrect things will not work.""",
|
||||
"""
|
||||
被隐藏的物品组件类型列表.
|
||||
该配置项接受一个物品组件列表, 格式不正确将不会启用."""));
|
||||
enabled = config.getBoolean(getBasePath() + ".enabled", enabled, config.pickStringRegionBased(
|
||||
"If enabled, specified item component information from player's inventory will be hided.",
|
||||
"启用后, 玩家背包内物品的指定组件信息会被隐藏."
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostLoaded() {
|
||||
final List<DataComponentType<?>> types = new ArrayList<>(hiddenTypeStrings.size());
|
||||
|
||||
for (String componentType : hiddenTypeStrings) {
|
||||
BuiltInRegistries.DATA_COMPONENT_TYPE.get(ResourceLocation.parse(componentType)).ifPresentOrElse(
|
||||
optional -> types.add(optional.value()),
|
||||
() -> LeafConfig.LOGGER.warn("Unknown component type: {}", componentType)
|
||||
);
|
||||
}
|
||||
|
||||
hiddenTypes = types;
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.gameplay;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class Knockback extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.GAMEPLAY.getBaseKeyName() + ".knockback";
|
||||
}
|
||||
|
||||
public static boolean snowballCanKnockback = false;
|
||||
public static boolean eggCanKnockback = false;
|
||||
public static boolean canPlayerKnockbackZombie = true;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
snowballCanKnockback = config.getBoolean(getBasePath() + ".snowball-knockback-players", snowballCanKnockback,
|
||||
config.pickStringRegionBased(
|
||||
"Make snowball can knockback players.",
|
||||
"使雪球可以击退玩家."
|
||||
));
|
||||
eggCanKnockback = config.getBoolean(getBasePath() + ".egg-knockback-players", eggCanKnockback,
|
||||
config.pickStringRegionBased(
|
||||
"Make egg can knockback players.",
|
||||
"使鸡蛋可以击退玩家."
|
||||
));
|
||||
canPlayerKnockbackZombie = config.getBoolean(getBasePath() + ".can-player-knockback-zombie", canPlayerKnockbackZombie,
|
||||
config.pickStringRegionBased(
|
||||
"Make players can knockback zombie.",
|
||||
"使玩家可以击退僵尸."
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.gameplay;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class OnlyPlayerPushable extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.GAMEPLAY.getBaseKeyName() + ".only-player-pushable";
|
||||
}
|
||||
|
||||
public static boolean enabled = false;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
enabled = config.getBoolean(getBasePath(), enabled, config.pickStringRegionBased(
|
||||
"Enable to make only player pushable",
|
||||
"是否只允许玩家被实体推动"
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.gameplay;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
import org.dreeam.leaf.config.annotations.Experimental;
|
||||
|
||||
public class SmoothTeleport extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.GAMEPLAY.getBaseKeyName() + ".smooth-teleport";
|
||||
}
|
||||
|
||||
@Experimental
|
||||
public static boolean enabled = false;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
enabled = config.getBoolean(getBasePath(), enabled, config.pickStringRegionBased(
|
||||
"""
|
||||
**Experimental feature**
|
||||
Whether to make a "smooth teleport" when players changing dimension.
|
||||
This requires original world and target world have same logical height to work.""",
|
||||
"""
|
||||
**实验性功能**
|
||||
是否在玩家切换世界时尝试使用 "平滑传送".
|
||||
此项要求源世界和目标世界逻辑高度相同才会生效."""
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.gameplay;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class SpawnerSettings extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.GAMEPLAY.getBaseKeyName() + ".spawner-settings";
|
||||
}
|
||||
|
||||
// Global toggle
|
||||
public static boolean enabled = false;
|
||||
|
||||
// Default values for spawner settings
|
||||
public static boolean lightLevelCheck = false;
|
||||
public static boolean spawnerMaxNearbyCheck = true;
|
||||
public static boolean checkForNearbyPlayers = true;
|
||||
public static boolean spawnerBlockChecks = false;
|
||||
public static boolean waterPreventSpawnCheck = false;
|
||||
|
||||
public static int minSpawnDelay = 200;
|
||||
public static int maxSpawnDelay = 800;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
config.addCommentRegionBased(getBasePath(),
|
||||
"This section contains settings for mob spawner blocks.",
|
||||
"此部分包含刷怪笼生物生成的设置.");
|
||||
|
||||
// Global toggle
|
||||
enabled = config.getBoolean(getBasePath() + ".enabled", enabled,
|
||||
config.pickStringRegionBased(
|
||||
"Enable custom spawner settings. Set to true to enable all features below.",
|
||||
"启用自定义刷怪笼设置. 设为 true 以启用以下所有功能."
|
||||
));
|
||||
|
||||
// Checks section
|
||||
config.addCommentRegionBased(getBasePath() + ".checks",
|
||||
"Various checks that can be enabled or disabled for spawner blocks.",
|
||||
"可以为刷怪笼启用或禁用的各种检查.");
|
||||
|
||||
lightLevelCheck = config.getBoolean(getBasePath() + ".checks.light-level-check", lightLevelCheck,
|
||||
config.pickStringRegionBased(
|
||||
"Check if there is the required light level to spawn the mob",
|
||||
"检查是否有所需的光照等级来生成怪物"
|
||||
));
|
||||
|
||||
spawnerMaxNearbyCheck = config.getBoolean(getBasePath() + ".checks.spawner-max-nearby-check", spawnerMaxNearbyCheck,
|
||||
config.pickStringRegionBased(
|
||||
"Check if there are the max amount of nearby mobs to spawn the mob",
|
||||
"检查附近是否已达到最大怪物数量限制"
|
||||
));
|
||||
|
||||
checkForNearbyPlayers = config.getBoolean(getBasePath() + ".checks.check-for-nearby-players", checkForNearbyPlayers,
|
||||
config.pickStringRegionBased(
|
||||
"Check if any players are in a radius to spawn the mob",
|
||||
"检查是否有玩家在生成怪物的半径范围内"
|
||||
));
|
||||
|
||||
spawnerBlockChecks = config.getBoolean(getBasePath() + ".checks.spawner-block-checks", spawnerBlockChecks,
|
||||
config.pickStringRegionBased(
|
||||
"Check if there are blocks blocking the spawner to spawn the mob",
|
||||
"检查是否有方块阻挡刷怪笼生成怪物"
|
||||
));
|
||||
|
||||
waterPreventSpawnCheck = config.getBoolean(getBasePath() + ".checks.water-prevent-spawn-check", waterPreventSpawnCheck,
|
||||
config.pickStringRegionBased(
|
||||
"Checks if there is water around that prevents spawning",
|
||||
"检查周围是否有水阻止生成"
|
||||
));
|
||||
|
||||
// Delay settings
|
||||
|
||||
minSpawnDelay = config.getInt(getBasePath() + ".min-spawn-delay", minSpawnDelay,
|
||||
config.pickStringRegionBased(
|
||||
"Minimum delay (in ticks) between spawner spawns. Higher values slow down spawners.",
|
||||
"刷怪笼生成怪物之间的最小延迟 (以刻为单位). 较高的值会减缓刷怪笼的速度."
|
||||
));
|
||||
|
||||
maxSpawnDelay = config.getInt(getBasePath() + ".max-spawn-delay", maxSpawnDelay,
|
||||
config.pickStringRegionBased(
|
||||
"Maximum delay (in ticks) between spawner spawns. Higher values slow down spawners.",
|
||||
"刷怪笼生成怪物之间的最大延迟 (以刻为单位). 较高的值会减缓刷怪笼的速度."
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.gameplay;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class UseSpigotItemMergingMech extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.GAMEPLAY.getBaseKeyName() + ".use-spigot-item-merging-mechanism";
|
||||
}
|
||||
|
||||
public static boolean enabled = true;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
enabled = config.getBoolean(getBasePath(), enabled);
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.misc;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class Cache extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.MISC.getBaseKeyName() + ".cache";
|
||||
}
|
||||
|
||||
public static boolean cachePlayerProfileResult = true;
|
||||
public static int cachePlayerProfileResultTimeout = 1440;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
cachePlayerProfileResult = config.getBoolean(getBasePath() + ".cache-player-profile-result", cachePlayerProfileResult, config.pickStringRegionBased("""
|
||||
Cache the player profile result on they first join.
|
||||
It's useful if Mojang's verification server is down.""",
|
||||
"""
|
||||
玩家首次加入时缓存 PlayerProfile.
|
||||
正版验证服务器宕机时非常有用."""));
|
||||
cachePlayerProfileResultTimeout = config.getInt(getBasePath() + ".cache-player-profile-result-timeout", cachePlayerProfileResultTimeout,
|
||||
config.pickStringRegionBased(
|
||||
"The timeout of the cache. Unit: Minutes.",
|
||||
"缓存过期时间. 单位: 分钟."
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.misc;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class ConnectionMessage extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.MISC.getBaseKeyName() + ".connection-message";
|
||||
}
|
||||
|
||||
public static boolean joinEnabled = true;
|
||||
public static String joinMessage = "default";
|
||||
public static boolean quitEnabled = true;
|
||||
public static String quitMessage = "default";
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
config.addCommentRegionBased(getBasePath(), """
|
||||
Connection message, using MiniMessage format, set to "default" to use vanilla join message.
|
||||
available placeholders:
|
||||
%player_name% - player name
|
||||
%player_displayname% - player display name""",
|
||||
"""
|
||||
自定义加入 & 退出消息 (MiniMessage 格式), 设置为 'default' 将使用原版消息.
|
||||
可用的内置变量:
|
||||
%player_name% - 玩家名称
|
||||
%player_displayname% - 玩家显示名称""");
|
||||
|
||||
joinEnabled = config.getBoolean(getBasePath() + ".join.enabled", joinEnabled);
|
||||
joinMessage = config.getString(getBasePath() + ".join.message", joinMessage, config.pickStringRegionBased(
|
||||
"Join message of player",
|
||||
"玩家加入服务器时的消息"
|
||||
));
|
||||
|
||||
quitEnabled = config.getBoolean(getBasePath() + ".quit.enabled", quitEnabled);
|
||||
quitMessage = config.getString(getBasePath() + ".quit.message", quitMessage, config.pickStringRegionBased(
|
||||
"Quit message of player",
|
||||
"玩家退出服务器时的消息"));
|
||||
|
||||
// Legacy compatibility
|
||||
// TODO: config migration
|
||||
joinMessage = joinMessage
|
||||
.replace("%player_name%", "<player_name>")
|
||||
.replace("%player_displayname%", "<player_displayname>");
|
||||
quitMessage = quitMessage
|
||||
.replace("%player_name%", "<player_name>")
|
||||
.replace("%player_displayname%", "<player_displayname>");
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.misc;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class Including5sIngetTPS extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.MISC.getBaseKeyName();
|
||||
}
|
||||
|
||||
public static boolean enabled = true;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
enabled = config.getBoolean(getBasePath() + ".including-5s-in-get-tps", enabled);
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.misc;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class LagCompensation extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.MISC.getBaseKeyName() + ".lag-compensation";
|
||||
}
|
||||
|
||||
public static boolean enabled = false;
|
||||
public static boolean enableForWater = false;
|
||||
public static boolean enableForLava = false;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
config.addCommentRegionBased(getBasePath(), """
|
||||
This section contains lag compensation features,
|
||||
which could ensure basic playing experience during a lag.""",
|
||||
"""
|
||||
这部分包含滞后补偿功能,
|
||||
可以在卡顿情况下保障基本游戏体验.""");
|
||||
|
||||
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
|
||||
enableForWater = config.getBoolean(getBasePath() + ".enable-for-water", enableForWater);
|
||||
enableForLava = config.getBoolean(getBasePath() + ".enable-for-lava", enableForLava);
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.misc;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
import org.dreeam.leaf.config.annotations.DoNotLoad;
|
||||
import org.slf4j.Logger;
|
||||
import org.stupidcraft.linearpaper.region.EnumRegionFileExtension;
|
||||
|
||||
public class RegionFormatConfig extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.MISC.getBaseKeyName() + ".region-format-settings";
|
||||
}
|
||||
|
||||
@DoNotLoad
|
||||
private static final Logger logger = LogUtils.getLogger();
|
||||
@DoNotLoad
|
||||
public static EnumRegionFileExtension regionFormatType;
|
||||
|
||||
public static String regionFormatTypeName = "MCA";
|
||||
public static int linearCompressionLevel = 1;
|
||||
public static boolean throwOnUnknownExtension = false;
|
||||
public static int linearFlushFrequency = 5;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
config.addCommentRegionBased(getBasePath(), """
|
||||
Linear is a region file format that uses ZSTD compression instead of
|
||||
ZLIB.
|
||||
This format saves about 50% of disk space.
|
||||
Read Documentation before using: https://github.com/xymb-endcrystalme/LinearRegionFileFormatTools
|
||||
Disclaimer: This is an experimental feature, there is potential risk to lose chunk data.
|
||||
So backup your server before switching to Linear.""",
|
||||
"""
|
||||
Linear 是一种使用 ZSTD 压缩而非 ZLIB 的区域文件格式.
|
||||
该格式可节省约 50% 的磁盘空间.
|
||||
使用前请阅读文档: https://github.com/xymb-endcrystalme/LinearRegionFileFormatTools
|
||||
免责声明: 实验性功能,有可能导致区块数据丢失.
|
||||
切换到Linear前请备份服务器.""");
|
||||
|
||||
regionFormatTypeName = config.getString(getBasePath() + ".region-format", regionFormatTypeName,
|
||||
config.pickStringRegionBased(
|
||||
"Available region formats: MCA, LINEAR",
|
||||
"可用格式: MCA, LINEAR"));
|
||||
linearCompressionLevel = config.getInt(getBasePath() + ".linear-compress-level", linearCompressionLevel);
|
||||
throwOnUnknownExtension = config.getBoolean(getBasePath() + ".throw-on-unknown-extension-detected", throwOnUnknownExtension);
|
||||
linearFlushFrequency = config.getInt(getBasePath() + ".flush-interval-seconds", linearFlushFrequency);
|
||||
|
||||
regionFormatType = EnumRegionFileExtension.fromName(regionFormatTypeName);
|
||||
if (regionFormatType == EnumRegionFileExtension.UNKNOWN) {
|
||||
logger.error("Unknown region file type {} ! Falling back to MCA format.", regionFormatTypeName);
|
||||
regionFormatType = EnumRegionFileExtension.MCA;
|
||||
}
|
||||
|
||||
if (linearCompressionLevel > 23 || linearCompressionLevel < 1) {
|
||||
logger.error("Linear region compression level should be between 1 and 22 in config: {}", linearCompressionLevel);
|
||||
logger.error("Falling back to compression level 1.");
|
||||
linearCompressionLevel = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.misc;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class RemoveChangeNonEditableSignWarning extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.MISC.getBaseKeyName();
|
||||
}
|
||||
|
||||
public static boolean enabled = false;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
enabled = config.getBoolean(getBasePath() + ".remove-change-non-editable-sign-warning", enabled,
|
||||
config.pickStringRegionBased(
|
||||
"Enable to prevent console spam.",
|
||||
"移除修改无法编辑的告示牌时输出的警告."
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.misc;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class RemoveSpigotCheckBungee extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.MISC.getBaseKeyName() + ".remove-spigot-check-bungee-config";
|
||||
}
|
||||
|
||||
public static boolean enabled = true;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
enabled = config.getBoolean(getBasePath(), enabled, config.pickStringRegionBased("""
|
||||
Enable player enter backend server through proxy
|
||||
without backend server enabling its bungee mode.""",
|
||||
"""
|
||||
使服务器无需打开 bungee 模式即可让玩家加入后端服务器."""));
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.misc;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class RemoveVanillaUsernameCheck extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.MISC.getBaseKeyName() + ".remove-vanilla-username-check";
|
||||
}
|
||||
|
||||
public static boolean enabled = true;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
enabled = config.getBoolean(getBasePath(), enabled, config.pickStringRegionBased("""
|
||||
Remove Vanilla username check,
|
||||
allowing all characters as username.""",
|
||||
"""
|
||||
移除原版的用户名验证,
|
||||
让所有字符均可作为玩家名."""));
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.misc;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class SecureSeed extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.MISC.getBaseKeyName() + ".secure-seed";
|
||||
}
|
||||
|
||||
public static boolean enabled = false;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
config.addCommentRegionBased(getBasePath(), """
|
||||
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.""",
|
||||
"""
|
||||
安全种子开启后, 所有矿物与结构都将使用1024位的种子进行生成, 无法被破解.""");
|
||||
|
||||
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.misc;
|
||||
|
||||
import org.apache.logging.log4j.Level;
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class SentryDSN extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.MISC.getBaseKeyName() + ".sentry";
|
||||
}
|
||||
|
||||
public static String sentryDsnConfigPath;
|
||||
public static String sentryDsn = "";
|
||||
public static String logLevel = "WARN";
|
||||
public static boolean onlyLogThrown = true;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
String sentryEnvironment = System.getenv("SENTRY_DSN");
|
||||
String sentryConfig = config.getString(sentryDsnConfigPath = getBasePath() + ".dsn", sentryDsn, config.pickStringRegionBased("""
|
||||
Sentry DSN for improved error logging, leave blank to disable,
|
||||
Obtain from https://sentry.io/""",
|
||||
"""
|
||||
Sentry DSN (出现严重错误时将发送至配置的Sentry DSN地址) (留空关闭)"""));
|
||||
|
||||
sentryDsn = sentryEnvironment == null
|
||||
? sentryConfig
|
||||
: sentryEnvironment;
|
||||
logLevel = config.getString(getBasePath() + ".log-level", logLevel, config.pickStringRegionBased("""
|
||||
Logs with a level higher than or equal to this level will be recorded.""",
|
||||
"""
|
||||
大于等于该等级的日志会被记录."""));
|
||||
onlyLogThrown = config.getBoolean(getBasePath() + ".only-log-thrown", onlyLogThrown, config.pickStringRegionBased("""
|
||||
Only log with a Throwable will be recorded after enabling this.""",
|
||||
"""
|
||||
是否仅记录带有 Throwable 的日志."""));
|
||||
|
||||
if (sentryDsn != null && !sentryDsn.isBlank()) {
|
||||
gg.pufferfish.pufferfish.sentry.SentryManager.init(Level.getLevel(logLevel));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.misc;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class ServerBrand extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.MISC.getBaseKeyName() + ".rebrand";
|
||||
}
|
||||
|
||||
public static String serverModName = io.papermc.paper.ServerBuildInfo.buildInfo().brandName();
|
||||
public static String serverGUIName = "Leaf Console";
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
serverModName = config.getString(getBasePath() + ".server-mod-name", serverModName);
|
||||
serverGUIName = config.getString(getBasePath() + ".server-gui-name", serverGUIName);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.misc;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class UnknownCommandMessage extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.MISC.getBaseKeyName() + ".message";
|
||||
}
|
||||
|
||||
public static String unknownCommandMessage = "<red><lang:command.unknown.command><newline><detail>";
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
unknownCommandMessage = config.getString(getBasePath() + ".unknown-command", unknownCommandMessage, config.pickStringRegionBased("""
|
||||
Unknown command message, using MiniMessage format, set to "default" to use vanilla message,
|
||||
placeholder: <detail>, shows detail of the unknown command information.""",
|
||||
"""
|
||||
发送未知命令时的消息, 使用 MiniMessage 格式, 设置为 "default" 使用原版消息.
|
||||
变量: <detail>, 显示未知命令详细信息."""));
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.network;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class ChatMessageSignature extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.NETWORK.getBaseKeyName();
|
||||
}
|
||||
|
||||
public static boolean enabled = true;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
enabled = config.getBoolean(getBasePath() + ".chat-message-signature", enabled, config.pickStringRegionBased("""
|
||||
Whether or not enable chat message signature,
|
||||
disable will prevent players to report chat messages.
|
||||
And also disables the popup when joining a server without
|
||||
'secure chat', such as offline-mode servers.
|
||||
""",
|
||||
"""
|
||||
是否启用聊天签名, 禁用后玩家无法进行聊天举报."""));
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.network;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class ConnectionFlushQueueRewrite extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.NETWORK.getBaseKeyName();
|
||||
}
|
||||
|
||||
public static boolean enabled = false;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
enabled = config.getBoolean(getBasePath() + ".connection-flush-queue-rewrite", enabled, config.pickStringRegionBased("""
|
||||
This replaces ConcurrentLinkedQueue with ArrayDeque for better performance
|
||||
and uses the Netty event loop to ensure thread safety.
|
||||
|
||||
May increase the Netty thread usage and requires server restart to take effect
|
||||
Default: false
|
||||
""",
|
||||
"""
|
||||
此选项将 ConcurrentLinkedQueue 替换为 ArrayDeque 以提高性能,
|
||||
并使用 Netty 事件循环以确保线程安全。
|
||||
|
||||
默认值: false
|
||||
"""));
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.network;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class ProtocolSupport extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.NETWORK.getBaseKeyName() + ".protocol-support";
|
||||
}
|
||||
|
||||
public static boolean jadeProtocol = false;
|
||||
public static boolean appleskinProtocol = false;
|
||||
public static int appleskinSyncTickInterval = 20;
|
||||
public static boolean asteorBarProtocol = false;
|
||||
public static boolean chatImageProtocol = false;
|
||||
public static boolean xaeroMapProtocol = false;
|
||||
public static int xaeroMapServerID = ThreadLocalRandom.current().nextInt(); // Leaf - Faster Random
|
||||
public static boolean syncmaticaProtocol = false;
|
||||
public static boolean syncmaticaQuota = false;
|
||||
public static int syncmaticaQuotaLimit = 40000000;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
jadeProtocol = config.getBoolean(getBasePath() + ".jade-protocol", jadeProtocol);
|
||||
appleskinProtocol = config.getBoolean(getBasePath() + ".appleskin-protocol", appleskinProtocol);
|
||||
appleskinSyncTickInterval = config.getInt(getBasePath() + ".appleskin-protocol-sync-tick-interval", appleskinSyncTickInterval);
|
||||
asteorBarProtocol = config.getBoolean(getBasePath() + ".asteorbar-protocol", asteorBarProtocol);
|
||||
chatImageProtocol = config.getBoolean(getBasePath() + ".chatimage-protocol", chatImageProtocol);
|
||||
xaeroMapProtocol = config.getBoolean(getBasePath() + ".xaero-map-protocol", xaeroMapProtocol);
|
||||
xaeroMapServerID = config.getInt(getBasePath() + ".xaero-map-server-id", xaeroMapServerID);
|
||||
syncmaticaProtocol = config.getBoolean(getBasePath() + ".syncmatica-protocol", syncmaticaProtocol);
|
||||
syncmaticaQuota = config.getBoolean(getBasePath() + ".syncmatica-quota", syncmaticaQuota);
|
||||
syncmaticaQuotaLimit = config.getInt(getBasePath() + ".syncmatica-quota-limit", syncmaticaQuotaLimit);
|
||||
|
||||
if (syncmaticaProtocol) {
|
||||
org.leavesmc.leaves.protocol.syncmatica.SyncmaticaProtocol.init();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.opt;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class BrainRunningBehaviorCacheUpdate extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.PERF.getBaseKeyName();
|
||||
}
|
||||
|
||||
public static int interval = 5;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
interval = config.getInt(getBasePath() + ".entity-running-behavior-cache-update-interval", interval,
|
||||
config.pickStringRegionBased(
|
||||
"How often entity update current brain running behavior list.",
|
||||
"生物更新现有 Brain Behavior 列表缓存的间隔."));
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.opt;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class DontSaveEntity extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.PERF.getBaseKeyName() + ".dont-save-entity";
|
||||
}
|
||||
|
||||
public static boolean dontSavePrimedTNT = false;
|
||||
public static boolean dontSaveFallingBlock = false;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
dontSavePrimedTNT = config.getBoolean(getBasePath() + ".dont-save-primed-tnt", dontSavePrimedTNT,
|
||||
config.pickStringRegionBased(
|
||||
"""
|
||||
Disable save primed tnt on chunk unloads.
|
||||
Useful for redstone/technical servers, can prevent machines from being exploded by TNT,
|
||||
when player disconnected caused by Internet issue.""",
|
||||
"""
|
||||
区块卸载时不保存掉落的方块和激活的 TNT,
|
||||
可以避免在玩家掉线时机器被炸毁."""));
|
||||
dontSaveFallingBlock = config.getBoolean(getBasePath() + ".dont-save-falling-block", dontSaveFallingBlock);
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.opt;
|
||||
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
import org.dreeam.leaf.config.LeafConfig;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class DynamicActivationofBrain extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.PERF.getBaseKeyName() + ".dab";
|
||||
}
|
||||
|
||||
public static boolean enabled = true;
|
||||
public static int startDistance = 12;
|
||||
public static int startDistanceSquared;
|
||||
public static int maximumActivationPrio = 20;
|
||||
public static int activationDistanceMod = 8;
|
||||
public static boolean dontEnableIfInWater = false;
|
||||
public static List<String> blackedEntities = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
config.addCommentRegionBased(getBasePath(), """
|
||||
Optimizes entity brains when
|
||||
they're far away from the player""",
|
||||
"""
|
||||
根据距离动态优化生物 AI""");
|
||||
|
||||
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
|
||||
dontEnableIfInWater = config.getBoolean(getBasePath() + ".dont-enable-if-in-water", dontEnableIfInWater, config.pickStringRegionBased("""
|
||||
After enabling this, non-aquatic entities in the water will not be affected by DAB.
|
||||
This could fix entities suffocate in the water.""",
|
||||
"""
|
||||
启用此项后, 在水中的非水生生物将不会被 DAB 影响.
|
||||
可以避免距离玩家较远的生物在水里淹死."""));
|
||||
startDistance = config.getInt(getBasePath() + ".start-distance", startDistance, config.pickStringRegionBased("""
|
||||
This value determines how far away an entity has to be
|
||||
from the player to start being effected by DEAR.""",
|
||||
"""
|
||||
生物距离玩家多少格 DAB 开始生效"""));
|
||||
maximumActivationPrio = config.getInt(getBasePath() + ".max-tick-freq", maximumActivationPrio, config.pickStringRegionBased("""
|
||||
This value defines how often in ticks, the furthest entity
|
||||
will get their pathfinders and behaviors ticked. 20 = 1s""",
|
||||
"""
|
||||
最远处的实体每隔多少刻tick一次"""));
|
||||
activationDistanceMod = config.getInt(getBasePath() + ".activation-dist-mod", activationDistanceMod, """
|
||||
This value defines how much distance modifies an entity's
|
||||
tick frequency. freq = (distanceToPlayer^2) / (2^value)",
|
||||
If you want further away entities to tick less often, use 7.
|
||||
If you want further away entities to tick more often, try 9.""");
|
||||
blackedEntities = config.getList(getBasePath() + ".blacklisted-entities", blackedEntities,
|
||||
config.pickStringRegionBased("A list of entities to ignore for activation",
|
||||
"不会被 DAB 影响的实体列表"));
|
||||
|
||||
startDistanceSquared = startDistance * startDistance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostLoaded() {
|
||||
for (EntityType<?> entityType : BuiltInRegistries.ENTITY_TYPE) {
|
||||
entityType.dabEnabled = true; // reset all, before setting the ones to true
|
||||
}
|
||||
|
||||
final String DEFAULT_PREFIX = ResourceLocation.DEFAULT_NAMESPACE + ResourceLocation.NAMESPACE_SEPARATOR;
|
||||
|
||||
for (String name : blackedEntities) {
|
||||
// Be compatible with both `minecraft:example` and `example` syntax
|
||||
// If unknown, show user config value in the logger instead of parsed result
|
||||
String lowerName = name.toLowerCase(Locale.ROOT);
|
||||
String typeId = lowerName.startsWith(DEFAULT_PREFIX) ? lowerName : DEFAULT_PREFIX + lowerName;
|
||||
|
||||
EntityType.byString(typeId).ifPresentOrElse(entityType ->
|
||||
entityType.dabEnabled = false,
|
||||
() -> LeafConfig.LOGGER.warn("Skip unknown entity {}, in {}", name, getBasePath() + ".blacklisted-entities")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.opt;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class EnableCachedMTBEntityTypeConvert extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.PERF.getBaseKeyName();
|
||||
}
|
||||
|
||||
public static boolean enabled = true;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
enabled = config.getBoolean(getBasePath() + ".enable-cached-minecraft-to-bukkit-entitytype-convert", enabled, config.pickStringRegionBased("""
|
||||
Whether to cache expensive CraftEntityType#minecraftToBukkit call.""",
|
||||
"""
|
||||
是否缓存Minecraft到Bukkit的实体类型转换."""));
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.opt;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
import org.dreeam.leaf.config.LeafConfig;
|
||||
|
||||
import java.util.random.RandomGeneratorFactory;
|
||||
|
||||
public class FastRNG extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.PERF.getBaseKeyName() + ".faster-random-generator";
|
||||
}
|
||||
|
||||
public static boolean enabled = false;
|
||||
public static boolean enableForWorldgen = false;
|
||||
public static String randomGenerator = "Xoroshiro128PlusPlus";
|
||||
public static boolean warnForSlimeChunk = true;
|
||||
public static boolean useLegacyForSlimeChunk = false;
|
||||
public static boolean useDirectImpl = false;
|
||||
|
||||
public static boolean worldgenEnabled() {return enabled && enableForWorldgen;} // Helper function
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
config.addCommentRegionBased(getBasePath(), """
|
||||
Use faster random generator?
|
||||
Requires a JVM that supports RandomGenerator.
|
||||
Some JREs don't support this.""",
|
||||
"""
|
||||
是否使用更快的随机生成器?
|
||||
需要支持 RandomGenerator 的 JVM.
|
||||
一些 JRE 不支持此功能.""");
|
||||
|
||||
enabled = config.getBoolean(getBasePath() + ".enabled", enabled);
|
||||
randomGenerator = config.getString(getBasePath() + ".random-generator", randomGenerator,
|
||||
config.pickStringRegionBased(
|
||||
"""
|
||||
Which random generator will be used?
|
||||
See https://openjdk.org/jeps/356""",
|
||||
"""
|
||||
使用什么种类的随机生成器.
|
||||
请参阅 https://openjdk.org/jeps/356"""));
|
||||
enableForWorldgen = config.getBoolean(getBasePath() + ".enable-for-worldgen", enableForWorldgen,
|
||||
config.pickStringRegionBased(
|
||||
"""
|
||||
Enable faster random generator for world generation.
|
||||
WARNING: This will affect world generation!!!""",
|
||||
"""
|
||||
是否为世界生成启用更快的随机生成器.
|
||||
警告: 此项会影响世界生成!!!"""));
|
||||
warnForSlimeChunk = config.getBoolean(getBasePath() + ".warn-for-slime-chunk", warnForSlimeChunk,
|
||||
config.pickStringRegionBased(
|
||||
"Warn if you are not using legacy random source for slime chunk generation.",
|
||||
"是否在没有为史莱姆区块使用原版随机生成器的情况下进行警告."));
|
||||
useLegacyForSlimeChunk = config.getBoolean(getBasePath() + ".use-legacy-random-for-slime-chunk", useLegacyForSlimeChunk, config.pickStringRegionBased(
|
||||
"""
|
||||
Use legacy random source for slime chunk generation,
|
||||
to follow vanilla behavior.""",
|
||||
"""
|
||||
是否使用原版随机生成器来生成史莱姆区块."""));
|
||||
useDirectImpl = config.getBoolean(getBasePath() + ".use-direct-implementation", useDirectImpl,
|
||||
config.pickStringRegionBased(
|
||||
"""
|
||||
Use direct random implementation instead of delegating to Java's RandomGenerator.
|
||||
This may improve performance but potentially changes RNG behavior.""",
|
||||
"""
|
||||
使用直接随机实现而不是委托给Java的RandomGenerator.
|
||||
这可能会提高性能,但可能会改变RNG行为。"""));
|
||||
|
||||
if (enabled) {
|
||||
try {
|
||||
RandomGeneratorFactory.of(randomGenerator);
|
||||
} catch (Exception e) {
|
||||
LeafConfig.LOGGER.error("Faster random generator is enabled but {} is not supported by your JVM, " +
|
||||
"falling back to legacy random source.", randomGenerator);
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (enabled && warnForSlimeChunk) {
|
||||
LeafConfig.LOGGER.warn("You enabled faster random generator, it will offset location of slime chunk");
|
||||
LeafConfig.LOGGER.warn("If your server has slime farms or facilities need vanilla slime chunk,");
|
||||
LeafConfig.LOGGER.warn("set performance.faster-random-generator.use-legacy-random-for-slime-chunk " +
|
||||
"to true to use LegacyRandomSource for slime chunk generation.");
|
||||
LeafConfig.LOGGER.warn("Set performance.faster-random-generator.warn-for-slime-chunk to false to " +
|
||||
"disable this warning.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.opt;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class FasterStructureGenFutureSequencing extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.PERF.getBaseKeyName();
|
||||
}
|
||||
|
||||
public static boolean enabled = true;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
enabled = config.getBoolean(getBasePath() + ".faster-structure-gen-future-sequencing", enabled,
|
||||
config.pickStringRegionBased(
|
||||
"May cause the inconsistent order of future compose tasks.",
|
||||
"更快的结构生成任务分段."));
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.opt;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class ReduceUselessPackets extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.PERF.getBaseKeyName() + ".reduce-packets";
|
||||
}
|
||||
|
||||
public static boolean reduceUselessEntityMovePackets = false;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
reduceUselessEntityMovePackets = config.getBoolean(getBasePath() + ".reduce-entity-move-packets", reduceUselessEntityMovePackets);
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.opt;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class SkipAIForNonAwareMob extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.PERF.getBaseKeyName();
|
||||
}
|
||||
|
||||
public static boolean enabled = true;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
enabled = config.getBoolean(getBasePath() + ".skip-ai-for-non-aware-mob", enabled);
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.opt;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class SkipMapItemDataUpdates extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.PERF.getBaseKeyName();
|
||||
}
|
||||
|
||||
public static boolean enabled = true;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
enabled = config.getBoolean(getBasePath() + ".skip-map-item-data-updates-if-map-does-not-have-craftmaprenderer", enabled);
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.opt;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class ThrottleHopperWhenFull extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.PERF.getBaseKeyName() + ".throttle-hopper-when-full";
|
||||
}
|
||||
|
||||
public static boolean enabled = false;
|
||||
public static int skipTicks = 0;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
enabled = config.getBoolean(getBasePath() + ".enabled", enabled, config.pickStringRegionBased("""
|
||||
Throttles the hopper if target container is full.""",
|
||||
"""
|
||||
是否在目标容器已满时阻塞漏斗."""));
|
||||
skipTicks = config.getInt(getBasePath() + ".skip-ticks", skipTicks, config.pickStringRegionBased("""
|
||||
How many ticks to throttle when the Hopper is throttled.""",
|
||||
"""
|
||||
每次阻塞多少 tick."""));
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.opt;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class ThrottleInactiveGoalSelectorTick extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.PERF.getBaseKeyName();
|
||||
}
|
||||
|
||||
public static boolean enabled = true;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
enabled = config.getBoolean(getBasePath() + ".inactive-goal-selector-throttle", enabled, config.pickStringRegionBased("""
|
||||
Throttles the AI goal selector in entity inactive ticks.
|
||||
This can improve performance by a few percent, but has minor gameplay implications.""",
|
||||
"""
|
||||
是否在实体不活跃 tick 时阻塞 AI 目标选择器.
|
||||
有助于提升性能, 但对游戏有轻微影响."""));
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.opt;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class TileEntitySnapshotCreation extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.PERF.getBaseKeyName();
|
||||
}
|
||||
|
||||
public static boolean enabled = true;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
enabled = config.getBoolean(getBasePath() + ".create-snapshot-on-retrieving-blockstate", enabled);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.opt;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class VT4BukkitScheduler extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.PERF.getBaseKeyName();
|
||||
}
|
||||
|
||||
public static boolean enabled = true;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
enabled = config.getBoolean(getBasePath() + ".use-virtual-thread-for-async-scheduler", enabled,
|
||||
config.pickStringRegionBased(
|
||||
"Use the new Virtual Thread introduced in JDK 21 for CraftAsyncScheduler.",
|
||||
"是否为异步任务调度器使用虚拟线程."));
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.opt;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class VT4ChatExecutor extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.PERF.getBaseKeyName();
|
||||
}
|
||||
|
||||
public static boolean enabled = true;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
enabled = config.getBoolean(getBasePath() + ".use-virtual-thread-for-async-chat-executor", enabled,
|
||||
config.pickStringRegionBased(
|
||||
"Use the new Virtual Thread introduced in JDK 21 for Async Chat Executor.",
|
||||
"是否为异步聊天线程使用虚拟线程."));
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package org.dreeam.leaf.config.modules.opt;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class VT4UserAuthenticator extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.PERF.getBaseKeyName();
|
||||
}
|
||||
|
||||
public static boolean enabled = true;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
enabled = config.getBoolean(getBasePath() + ".use-virtual-thread-for-user-authenticator", enabled,
|
||||
config.pickStringRegionBased(
|
||||
"Use the new Virtual Thread introduced in JDK 21 for User Authenticator.",
|
||||
"是否为用户验证器使用虚拟线程."));
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
package org.dreeam.leaf.misc;
|
||||
|
||||
import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class LagCompensation {
|
||||
|
||||
public static float tt20(float ticks, boolean limitZero) {
|
||||
float newTicks = (float) rawTT20(ticks);
|
||||
|
||||
if (limitZero) return newTicks > 0 ? newTicks : 1;
|
||||
else return newTicks;
|
||||
}
|
||||
|
||||
public static int tt20(int ticks, boolean limitZero) {
|
||||
int newTicks = (int) Math.ceil(rawTT20(ticks));
|
||||
|
||||
if (limitZero) return newTicks > 0 ? newTicks : 1;
|
||||
else return newTicks;
|
||||
}
|
||||
|
||||
public static double tt20(double ticks, boolean limitZero) {
|
||||
double newTicks = rawTT20(ticks);
|
||||
|
||||
if (limitZero) return newTicks > 0 ? newTicks : 1;
|
||||
else return newTicks;
|
||||
}
|
||||
|
||||
public static double rawTT20(double ticks) {
|
||||
return ticks == 0 ? 0 : ticks * TPSCalculator.getMostAccurateTPS() / TPSCalculator.MAX_TPS;
|
||||
}
|
||||
|
||||
public static class TPSCalculator {
|
||||
|
||||
public static Long lastTick;
|
||||
public static Long currentTick;
|
||||
private static double allMissedTicks = 0;
|
||||
private static final List<Double> tpsHistory = Collections.synchronizedList(new DoubleArrayList());
|
||||
private static final int historyLimit = 40;
|
||||
|
||||
public static final int MAX_TPS = 20;
|
||||
public static final int FULL_TICK = 50;
|
||||
|
||||
private TPSCalculator() {
|
||||
}
|
||||
|
||||
public static void onTick() {
|
||||
if (currentTick != null) {
|
||||
lastTick = currentTick;
|
||||
}
|
||||
|
||||
currentTick = System.currentTimeMillis();
|
||||
|
||||
addToHistory(getTPS());
|
||||
clearMissedTicks();
|
||||
missedTick();
|
||||
}
|
||||
|
||||
private static void addToHistory(double tps) {
|
||||
if (tpsHistory.size() >= historyLimit) {
|
||||
tpsHistory.removeFirst();
|
||||
}
|
||||
|
||||
tpsHistory.add(tps);
|
||||
}
|
||||
|
||||
public static long getMSPT() {
|
||||
return currentTick - lastTick;
|
||||
}
|
||||
|
||||
public static double getAverageTPS() {
|
||||
double sum = 0.0;
|
||||
|
||||
for (double value : tpsHistory) {
|
||||
sum += value;
|
||||
}
|
||||
|
||||
return tpsHistory.isEmpty() ? 0.1 : sum / tpsHistory.size();
|
||||
}
|
||||
|
||||
public static double getTPS() {
|
||||
if (lastTick == null) return -1;
|
||||
if (getMSPT() <= 0) return 0.1;
|
||||
|
||||
double tps = 1000 / (double) getMSPT();
|
||||
|
||||
return tps > MAX_TPS ? MAX_TPS : tps;
|
||||
}
|
||||
|
||||
public static void missedTick() {
|
||||
if (lastTick == null) return;
|
||||
|
||||
long mspt = getMSPT() <= 0 ? 50 : getMSPT();
|
||||
double missedTicks = (mspt / (double) FULL_TICK) - 1;
|
||||
|
||||
allMissedTicks += missedTicks <= 0 ? 0 : missedTicks;
|
||||
}
|
||||
|
||||
public static double getMostAccurateTPS() {
|
||||
return Math.min(getTPS(), getAverageTPS());
|
||||
}
|
||||
|
||||
public double getAllMissedTicks() {
|
||||
return allMissedTicks;
|
||||
}
|
||||
|
||||
public static int applicableMissedTicks() {
|
||||
return (int) Math.floor(allMissedTicks);
|
||||
}
|
||||
|
||||
public static void clearMissedTicks() {
|
||||
allMissedTicks -= applicableMissedTicks();
|
||||
}
|
||||
|
||||
public void resetMissedTicks() {
|
||||
allMissedTicks = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package org.dreeam.leaf.util.biome;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class PositionalBiomeGetter implements Supplier<Holder<Biome>> {
|
||||
|
||||
private final Function<BlockPos, Holder<Biome>> biomeGetter;
|
||||
private final BlockPos.MutableBlockPos pos;
|
||||
private int nextX, nextY, nextZ;
|
||||
private volatile Holder<Biome> curBiome;
|
||||
|
||||
public PositionalBiomeGetter(Function<BlockPos, Holder<Biome>> biomeGetter, BlockPos.MutableBlockPos pos) {
|
||||
this.biomeGetter = biomeGetter;
|
||||
this.pos = pos;
|
||||
}
|
||||
|
||||
public void update(int nextX, int nextY, int nextZ) {
|
||||
this.nextX = nextX;
|
||||
this.nextY = nextY;
|
||||
this.nextZ = nextZ;
|
||||
this.curBiome = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Holder<Biome> get() {
|
||||
Holder<Biome> biome = curBiome;
|
||||
if (biome == null) {
|
||||
curBiome = biome = biomeGetter.apply(pos.set(nextX, nextY, nextZ));
|
||||
}
|
||||
return biome;
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package org.dreeam.leaf.util.cache;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
|
||||
import java.util.BitSet;
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
public class CachedOrNewBitsGetter {
|
||||
|
||||
private static final IntFunction<BitSet> BITSET_CONSTRUCTOR = BitSet::new;
|
||||
private static final ThreadLocal<Int2ObjectOpenHashMap<BitSet>> BITSETS = ThreadLocal.withInitial(Int2ObjectOpenHashMap::new);
|
||||
|
||||
private CachedOrNewBitsGetter() {
|
||||
}
|
||||
|
||||
public static BitSet getCachedOrNewBitSet(int bits) {
|
||||
final BitSet bitSet = BITSETS.get().computeIfAbsent(bits, BITSET_CONSTRUCTOR);
|
||||
|
||||
bitSet.clear();
|
||||
|
||||
return bitSet;
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package org.dreeam.leaf.util.cache;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import it.unimi.dsi.fastutil.longs.LongList;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
|
||||
/**
|
||||
* @author 2No2Name, original implemenation by SuperCoder7979 and Gegy1000
|
||||
*/
|
||||
public class IterateOutwardsCache {
|
||||
|
||||
//POS_ZERO must not be replaced with BlockPos.ORIGIN, otherwise iterateOutwards at BlockPos.ORIGIN will not use the cache
|
||||
public static final BlockPos POS_ZERO = new BlockPos(0, 0, 0);
|
||||
|
||||
|
||||
private final ConcurrentHashMap<Long, LongArrayList> table;
|
||||
private final int capacity;
|
||||
private final Random random;
|
||||
|
||||
public IterateOutwardsCache(int capacity) {
|
||||
this.capacity = capacity;
|
||||
this.table = new ConcurrentHashMap<>(31);
|
||||
this.random = new Random();
|
||||
}
|
||||
|
||||
private void fillPositionsWithIterateOutwards(LongList entry, int xRange, int yRange, int zRange) {
|
||||
// Add all positions to the cached list
|
||||
for (BlockPos pos : BlockPos.withinManhattan(POS_ZERO, xRange, yRange, zRange)) {
|
||||
entry.add(pos.asLong());
|
||||
}
|
||||
}
|
||||
|
||||
public LongList getOrCompute(int xRange, int yRange, int zRange) {
|
||||
long key = BlockPos.asLong(xRange, yRange, zRange);
|
||||
|
||||
LongArrayList entry = this.table.get(key);
|
||||
if (entry != null) {
|
||||
return entry;
|
||||
}
|
||||
|
||||
// Cache miss: compute and store
|
||||
entry = new LongArrayList(128);
|
||||
|
||||
this.fillPositionsWithIterateOutwards(entry, xRange, yRange, zRange);
|
||||
|
||||
//decrease the array size, as of now it won't be modified anymore anyways
|
||||
entry.trim();
|
||||
|
||||
//this might overwrite an entry as the same entry could have been computed and added during this thread's computation
|
||||
//we do not use computeIfAbsent, as it can delay other threads for too long
|
||||
Object previousEntry = this.table.put(key, entry);
|
||||
|
||||
|
||||
if (previousEntry == null && this.table.size() > this.capacity) {
|
||||
//prevent a memory leak by randomly removing about 1/8th of the elements when the exceed the desired capacity is exceeded
|
||||
final Iterator<Long> iterator = this.table.keySet().iterator();
|
||||
//prevent an unlikely infinite loop caused by another thread filling the table concurrently using counting
|
||||
for (int i = -this.capacity; iterator.hasNext() && i < 5; i++) {
|
||||
Long key2 = iterator.next();
|
||||
//random is not threadsafe, but it doesn't matter here, because we don't need quality random numbers
|
||||
if (this.random.nextInt(8) == 0 && key2 != key) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package org.dreeam.leaf.util.cache;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.LongIterator;
|
||||
import it.unimi.dsi.fastutil.longs.LongList;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
|
||||
/**
|
||||
* @author 2No2Name
|
||||
*/
|
||||
public class LongList2BlockPosMutableIterable implements Iterable<BlockPos> {
|
||||
|
||||
private final LongList positions;
|
||||
private final int xOffset, yOffset, zOffset;
|
||||
|
||||
public LongList2BlockPosMutableIterable(BlockPos offset, LongList posList) {
|
||||
this.xOffset = offset.getX();
|
||||
this.yOffset = offset.getY();
|
||||
this.zOffset = offset.getZ();
|
||||
this.positions = posList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<BlockPos> iterator() {
|
||||
return new Iterator<>() {
|
||||
|
||||
private final LongIterator it = LongList2BlockPosMutableIterable.this.positions.iterator();
|
||||
private final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos();
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return it.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public net.minecraft.core.BlockPos next() {
|
||||
long nextPos = this.it.nextLong();
|
||||
return this.pos.set(
|
||||
LongList2BlockPosMutableIterable.this.xOffset + BlockPos.getX(nextPos),
|
||||
LongList2BlockPosMutableIterable.this.yOffset + BlockPos.getY(nextPos),
|
||||
LongList2BlockPosMutableIterable.this.zOffset + BlockPos.getZ(nextPos));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
package org.dreeam.leaf.util.item;
|
||||
|
||||
import net.minecraft.core.component.DataComponentMap;
|
||||
import net.minecraft.core.component.DataComponentType;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.dreeam.leaf.config.modules.gameplay.HideItemComponent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* ItemStackStripper
|
||||
*
|
||||
* @author TheFloodDragon
|
||||
* @since 2025/2/4 19:04
|
||||
*/
|
||||
public class ItemStackStripper {
|
||||
|
||||
public static ItemStack strip(final ItemStack itemStack, final boolean copy) {
|
||||
if (!HideItemComponent.enabled || itemStack.isEmpty() || itemStack.getComponentsPatch().isEmpty())
|
||||
return itemStack;
|
||||
|
||||
final ItemStack copied = copy ? itemStack.copy() : itemStack;
|
||||
|
||||
// Remove specified types
|
||||
for (DataComponentType<?> type : HideItemComponent.hiddenTypes) {
|
||||
// Only remove, no others
|
||||
copied.remove(type);
|
||||
}
|
||||
|
||||
return copied;
|
||||
}
|
||||
|
||||
public static List<ItemStack> strip(final List<ItemStack> itemStacks, final boolean copy) {
|
||||
if (!HideItemComponent.enabled) return itemStacks;
|
||||
|
||||
final List<ItemStack> copiedItems = new ArrayList<>();
|
||||
|
||||
for (ItemStack itemStack : itemStacks) {
|
||||
if (itemStack.isEmpty() || itemStack.getComponentsPatch().isEmpty()) {
|
||||
copiedItems.add(itemStack);
|
||||
continue;
|
||||
}
|
||||
|
||||
final ItemStack copied = copy ? itemStack.copy() : itemStack;
|
||||
|
||||
// Remove specified types
|
||||
for (DataComponentType<?> type : HideItemComponent.hiddenTypes) {
|
||||
// Only remove, no others
|
||||
copied.remove(type);
|
||||
}
|
||||
|
||||
copiedItems.add(copied);
|
||||
}
|
||||
|
||||
return copiedItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if two ItemStacks are the same after stripping components
|
||||
*/
|
||||
public static boolean matchesStripped(ItemStack left, ItemStack right) {
|
||||
return left == right || (
|
||||
left.is(right.getItem()) && left.getCount() == right.getCount() &&
|
||||
(left.isEmpty() && right.isEmpty() || Objects.equals(strip(left.getComponents()), strip(right.getComponents())))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a new DataComponentMap with all hidden components removed
|
||||
*/
|
||||
private static DataComponentMap strip(final DataComponentMap map) {
|
||||
return map.filter(c -> !HideItemComponent.hiddenTypes.contains(c));
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
package org.dreeam.leaf.util.list;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import net.minecraft.world.level.block.entity.TickingBlockEntity;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* A list for ServerLevel's blockEntityTickers
|
||||
* <p>
|
||||
* This list behaves identically to ObjectArrayList, but it has an additional method, `removeAllByIndex`, that allows a list of integers to be passed indicating what
|
||||
* indexes should be deleted from the list
|
||||
* <p>
|
||||
* This is faster than using removeAll, since we don't need to compare the identity of each block entity, and faster than looping thru each index manually and deleting with remove,
|
||||
* since we don't need to resize the array every single remove.
|
||||
*/
|
||||
public final class BlockEntityTickersList extends ObjectArrayList<TickingBlockEntity> {
|
||||
|
||||
private final IntOpenHashSet toRemove = new IntOpenHashSet();
|
||||
private int startSearchFromIndex = -1;
|
||||
|
||||
/**
|
||||
* Creates a new array list with {@link #DEFAULT_INITIAL_CAPACITY} capacity.
|
||||
*/
|
||||
public BlockEntityTickersList() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new array list and fills it with a given collection.
|
||||
*
|
||||
* @param c a collection that will be used to fill the array list.
|
||||
*/
|
||||
public BlockEntityTickersList(final Collection<? extends TickingBlockEntity> c) {
|
||||
super(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks an entry as removed
|
||||
*
|
||||
* @param index the index of the item on the list to be marked as removed
|
||||
*/
|
||||
public void markAsRemoved(final int index) {
|
||||
// The block entities list always loop starting from 0, so we only need to check if the startSearchFromIndex is -1 and that's it
|
||||
if (this.startSearchFromIndex == -1)
|
||||
this.startSearchFromIndex = index;
|
||||
|
||||
this.toRemove.add(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes elements that have been marked as removed.
|
||||
*/
|
||||
public void removeMarkedEntries() {
|
||||
if (this.startSearchFromIndex == -1) // No entries in the list, skip
|
||||
return;
|
||||
|
||||
removeAllByIndex(startSearchFromIndex, toRemove);
|
||||
toRemove.clear();
|
||||
this.startSearchFromIndex = -1; // Reset the start search index
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes elements by their index.
|
||||
*/
|
||||
private void removeAllByIndex(final int startSearchFromIndex, final IntOpenHashSet c) { // can't use Set<Integer> because we want to avoid autoboxing when using contains
|
||||
final int requiredMatches = c.size();
|
||||
if (requiredMatches == 0)
|
||||
return; // exit early, we don't need to do anything
|
||||
|
||||
final Object[] a = this.a;
|
||||
int j = startSearchFromIndex;
|
||||
int matches = 0;
|
||||
for (int i = startSearchFromIndex; i < size; i++) { // If the user knows the first index to be removed, we can skip a lot of unnecessary comparsions
|
||||
if (!c.contains(i)) {
|
||||
// TODO: It can be possible to optimize this loop by tracking the start/finish and then using arraycopy to "skip" the elements,
|
||||
// this would optimize cases where the index to be removed are far apart, HOWEVER it does have a big performance impact if you are doing
|
||||
// "arraycopy" for each element
|
||||
a[j++] = a[i];
|
||||
} else {
|
||||
matches++;
|
||||
}
|
||||
|
||||
if (matches == requiredMatches) { // Exit the loop if we already removed everything, we don't need to check anything else
|
||||
// We need to update the final size here, because we know that we already found everything!
|
||||
// Because we know that the size must be currentSize - requiredMatches (because we have matched everything), let's update the value
|
||||
// However, we need to copy the rest of the stuff over
|
||||
if (i != (size - 1)) { // If it isn't the last index...
|
||||
// i + 1 because we want to copy the *next* element over
|
||||
// and the size - i - 1 is because we want to get the current size, minus the current index (which is i), and then - 1 because we want to copy -1 ahead (remember, we are adding +1 to copy the *next* element)
|
||||
System.arraycopy(a, i + 1, a, j, size - i - 1);
|
||||
}
|
||||
j = size - requiredMatches;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Arrays.fill(a, j, size, null);
|
||||
size = j;
|
||||
}
|
||||
}
|
||||
@@ -1,697 +0,0 @@
|
||||
package org.dreeam.leaf.util.map;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* Optimized thread-safe implementation of {@link LongSet} that uses striped locking
|
||||
* and primitive long arrays to minimize boxing/unboxing overhead.
|
||||
*/
|
||||
@SuppressWarnings({"unused", "deprecation"})
|
||||
public final class ConcurrentLongHashSet extends LongOpenHashSet implements LongSet {
|
||||
|
||||
// Number of lock stripes - higher number means more concurrency but more memory
|
||||
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
|
||||
|
||||
// Load factor - when to resize the hash table
|
||||
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
|
||||
|
||||
// Initial capacity per stripe
|
||||
private static final int DEFAULT_INITIAL_CAPACITY = 16;
|
||||
|
||||
// Array of segments (stripes)
|
||||
private final Segment[] segments;
|
||||
|
||||
// Total size, cached for faster size() operation
|
||||
private final AtomicInteger size;
|
||||
|
||||
/**
|
||||
* Creates a new empty concurrent long set with default parameters.
|
||||
*/
|
||||
public ConcurrentLongHashSet() {
|
||||
this(DEFAULT_CONCURRENCY_LEVEL * DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new concurrent long set with the specified parameters.
|
||||
*
|
||||
* @param initialCapacity the initial capacity
|
||||
* @param loadFactor the load factor
|
||||
* @param concurrencyLevel the concurrency level
|
||||
*/
|
||||
public ConcurrentLongHashSet(int initialCapacity, float loadFactor, int concurrencyLevel) {
|
||||
// Need to call super() even though we don't use its state
|
||||
super();
|
||||
|
||||
// Validate parameters
|
||||
if (initialCapacity < 0) {
|
||||
throw new IllegalArgumentException("Initial capacity must be positive");
|
||||
}
|
||||
if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
|
||||
throw new IllegalArgumentException("Load factor must be positive");
|
||||
}
|
||||
if (concurrencyLevel <= 0) {
|
||||
throw new IllegalArgumentException("Concurrency level must be positive");
|
||||
}
|
||||
|
||||
// Calculate segment count (power of 2)
|
||||
int segmentCount = 1;
|
||||
while (segmentCount < concurrencyLevel) {
|
||||
segmentCount <<= 1;
|
||||
}
|
||||
|
||||
// Calculate capacity per segment
|
||||
int segmentCapacity = Math.max(initialCapacity / segmentCount, DEFAULT_INITIAL_CAPACITY);
|
||||
|
||||
// Create segments
|
||||
this.segments = new Segment[segmentCount];
|
||||
for (int i = 0; i < segmentCount; i++) {
|
||||
this.segments[i] = new Segment(segmentCapacity, loadFactor);
|
||||
}
|
||||
|
||||
this.size = new AtomicInteger(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return size.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return size.get() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(long key) {
|
||||
Segment segment = segmentFor(key);
|
||||
int delta = segment.add(key) ? 1 : 0;
|
||||
if (delta > 0) {
|
||||
size.addAndGet(delta);
|
||||
}
|
||||
return delta > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(long key) {
|
||||
return segmentFor(key).contains(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(long key) {
|
||||
Segment segment = segmentFor(key);
|
||||
int delta = segment.remove(key) ? -1 : 0;
|
||||
if (delta < 0) {
|
||||
size.addAndGet(delta);
|
||||
}
|
||||
return delta < 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
for (Segment segment : segments) {
|
||||
segment.clear();
|
||||
}
|
||||
size.set(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull LongIterator iterator() {
|
||||
return new ConcurrentLongIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long[] toLongArray() {
|
||||
long[] result = new long[size()];
|
||||
int index = 0;
|
||||
for (Segment segment : segments) {
|
||||
index = segment.toLongArray(result, index);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long[] toArray(long[] array) {
|
||||
Objects.requireNonNull(array, "Array cannot be null");
|
||||
long[] result = toLongArray();
|
||||
if (array.length < result.length) {
|
||||
return result;
|
||||
}
|
||||
System.arraycopy(result, 0, array, 0, result.length);
|
||||
if (array.length > result.length) {
|
||||
array[result.length] = 0;
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Object @NotNull [] toArray() {
|
||||
Long[] result = new Long[size()];
|
||||
int index = 0;
|
||||
for (Segment segment : segments) {
|
||||
index = segment.toObjectArray(result, index);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public <T> T @NotNull [] toArray(@NotNull T @NotNull [] array) {
|
||||
Objects.requireNonNull(array, "Array cannot be null");
|
||||
Long[] result = new Long[size()];
|
||||
int index = 0;
|
||||
for (Segment segment : segments) {
|
||||
index = segment.toObjectArray(result, index);
|
||||
}
|
||||
|
||||
if (array.length < result.length) {
|
||||
return (T[]) result;
|
||||
}
|
||||
|
||||
System.arraycopy(result, 0, array, 0, result.length);
|
||||
if (array.length > result.length) {
|
||||
array[result.length] = null;
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(@NotNull Collection<?> collection) {
|
||||
Objects.requireNonNull(collection, "Collection cannot be null");
|
||||
for (Object o : collection) {
|
||||
if (o instanceof Long) {
|
||||
if (!contains(((Long) o).longValue())) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(@NotNull Collection<? extends Long> collection) {
|
||||
Objects.requireNonNull(collection, "Collection cannot be null");
|
||||
boolean modified = false;
|
||||
for (Long value : collection) {
|
||||
modified |= add(value);
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(@NotNull Collection<?> collection) {
|
||||
Objects.requireNonNull(collection, "Collection cannot be null");
|
||||
boolean modified = false;
|
||||
for (Object o : collection) {
|
||||
if (o instanceof Long) {
|
||||
modified |= remove(((Long) o).longValue());
|
||||
}
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(@NotNull Collection<?> collection) {
|
||||
Objects.requireNonNull(collection, "Collection cannot be null");
|
||||
|
||||
// Convert collection to a set of longs for faster lookups
|
||||
LongOpenHashSet toRetain = new LongOpenHashSet();
|
||||
for (Object o : collection) {
|
||||
if (o instanceof Long) {
|
||||
toRetain.add(((Long) o).longValue());
|
||||
}
|
||||
}
|
||||
|
||||
boolean modified = false;
|
||||
for (Segment segment : segments) {
|
||||
modified |= segment.retainAll(toRetain);
|
||||
}
|
||||
|
||||
if (modified) {
|
||||
// Recalculate size
|
||||
int newSize = 0;
|
||||
for (Segment segment : segments) {
|
||||
newSize += segment.size();
|
||||
}
|
||||
size.set(newSize);
|
||||
}
|
||||
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(LongCollection c) {
|
||||
Objects.requireNonNull(c, "Collection cannot be null");
|
||||
boolean modified = false;
|
||||
LongIterator iterator = c.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
modified |= add(iterator.nextLong());
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(LongCollection c) {
|
||||
Objects.requireNonNull(c, "Collection cannot be null");
|
||||
LongIterator iterator = c.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
if (!contains(iterator.nextLong())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(LongCollection c) {
|
||||
Objects.requireNonNull(c, "Collection cannot be null");
|
||||
boolean modified = false;
|
||||
LongIterator iterator = c.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
modified |= remove(iterator.nextLong());
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(LongCollection c) {
|
||||
Objects.requireNonNull(c, "Collection cannot be null");
|
||||
|
||||
// For LongCollection we can directly use it
|
||||
boolean modified = false;
|
||||
for (Segment segment : segments) {
|
||||
modified |= segment.retainAll(c);
|
||||
}
|
||||
|
||||
if (modified) {
|
||||
// Recalculate size
|
||||
int newSize = 0;
|
||||
for (Segment segment : segments) {
|
||||
newSize += segment.size();
|
||||
}
|
||||
size.set(newSize);
|
||||
}
|
||||
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof LongSet that)) return false;
|
||||
if (size() != that.size()) return false;
|
||||
return containsAll(that);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 0;
|
||||
for (Segment segment : segments) {
|
||||
hash += segment.hashCode();
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append('[');
|
||||
|
||||
LongIterator it = iterator();
|
||||
boolean hasNext = it.hasNext();
|
||||
while (hasNext) {
|
||||
sb.append(it.nextLong());
|
||||
hasNext = it.hasNext();
|
||||
if (hasNext) {
|
||||
sb.append(", ");
|
||||
}
|
||||
}
|
||||
|
||||
sb.append(']');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the segment for a given key.
|
||||
*/
|
||||
private Segment segmentFor(long key) {
|
||||
// Use high bits of hash to determine segment
|
||||
// This helps spread keys more evenly across segments
|
||||
return segments[(int) ((spread(key) >>> segmentShift()) & segmentMask())];
|
||||
}
|
||||
|
||||
/**
|
||||
* Spread bits to reduce clustering for keys with similar hash codes.
|
||||
*/
|
||||
private static long spread(long key) {
|
||||
long h = key;
|
||||
h ^= h >>> 32;
|
||||
h ^= h >>> 16;
|
||||
h ^= h >>> 8;
|
||||
return h;
|
||||
}
|
||||
|
||||
private int segmentShift() {
|
||||
return Integer.numberOfLeadingZeros(segments.length);
|
||||
}
|
||||
|
||||
private int segmentMask() {
|
||||
return segments.length - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* A segment is a striped portion of the hash set with its own lock.
|
||||
*/
|
||||
private static class Segment {
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
private long[] keys;
|
||||
private boolean[] used;
|
||||
private int size;
|
||||
private int threshold;
|
||||
private final float loadFactor;
|
||||
|
||||
Segment(int initialCapacity, float loadFactor) {
|
||||
int capacity = MathUtil.nextPowerOfTwo(initialCapacity);
|
||||
this.keys = new long[capacity];
|
||||
this.used = new boolean[capacity];
|
||||
this.size = 0;
|
||||
this.loadFactor = loadFactor;
|
||||
this.threshold = (int) (capacity * loadFactor);
|
||||
}
|
||||
|
||||
int size() {
|
||||
lock.lock();
|
||||
try {
|
||||
return size;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
boolean contains(long key) {
|
||||
lock.lock();
|
||||
try {
|
||||
int index = indexOf(key);
|
||||
return used[index] && keys[index] == key;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
boolean add(long key) {
|
||||
lock.lock();
|
||||
try {
|
||||
int index = indexOf(key);
|
||||
|
||||
// Key already exists
|
||||
if (used[index] && keys[index] == key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Insert key
|
||||
keys[index] = key;
|
||||
if (!used[index]) {
|
||||
used[index] = true;
|
||||
size++;
|
||||
|
||||
// Check if rehash is needed
|
||||
if (size > threshold) {
|
||||
rehash();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
boolean remove(long key) {
|
||||
lock.lock();
|
||||
try {
|
||||
int index = indexOf(key);
|
||||
|
||||
// Key not found
|
||||
if (!used[index] || keys[index] != key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Mark slot as unused
|
||||
used[index] = false;
|
||||
size--;
|
||||
|
||||
// If the next slot is also used, we need to handle the removal properly
|
||||
// to maintain the open addressing property
|
||||
// This rehashing serves as a "cleanup" after removal
|
||||
if (size > 0) {
|
||||
rehashFromIndex(index);
|
||||
}
|
||||
|
||||
return true;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void clear() {
|
||||
lock.lock();
|
||||
try {
|
||||
for (int i = 0; i < used.length; i++) {
|
||||
used[i] = false;
|
||||
}
|
||||
size = 0;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
int toLongArray(long[] array, int offset) {
|
||||
lock.lock();
|
||||
try {
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
if (used[i]) {
|
||||
array[offset++] = keys[i];
|
||||
}
|
||||
}
|
||||
return offset;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
int toObjectArray(Long[] array, int offset) {
|
||||
lock.lock();
|
||||
try {
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
if (used[i]) {
|
||||
array[offset++] = keys[i];
|
||||
}
|
||||
}
|
||||
return offset;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
boolean retainAll(LongCollection toRetain) {
|
||||
lock.lock();
|
||||
try {
|
||||
boolean modified = false;
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
if (used[i] && !toRetain.contains(keys[i])) {
|
||||
used[i] = false;
|
||||
size--;
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Rehash to clean up if needed
|
||||
if (modified && size > 0) {
|
||||
rehash();
|
||||
}
|
||||
|
||||
return modified;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the index where a key should be stored.
|
||||
* Uses linear probing for collision resolution.
|
||||
*/
|
||||
private int indexOf(long key) {
|
||||
int mask = keys.length - 1;
|
||||
int index = (int) (spread(key) & mask);
|
||||
|
||||
while (used[index] && keys[index] != key) {
|
||||
index = (index + 1) & mask;
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rehash the segment with a larger capacity.
|
||||
*/
|
||||
private void rehash() {
|
||||
int oldCapacity = keys.length;
|
||||
int newCapacity = oldCapacity * 2;
|
||||
|
||||
long[] oldKeys = keys;
|
||||
boolean[] oldUsed = used;
|
||||
|
||||
keys = new long[newCapacity];
|
||||
used = new boolean[newCapacity];
|
||||
size = 0;
|
||||
threshold = (int) (newCapacity * loadFactor);
|
||||
|
||||
// Re-add all keys
|
||||
for (int i = 0; i < oldCapacity; i++) {
|
||||
if (oldUsed[i]) {
|
||||
add(oldKeys[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rehash from a specific index after removal to maintain proper open addressing.
|
||||
*/
|
||||
private void rehashFromIndex(int startIndex) {
|
||||
int mask = keys.length - 1;
|
||||
int currentIndex = startIndex;
|
||||
int nextIndex = (currentIndex + 1) & mask;
|
||||
|
||||
// For each cluster of used slots following the removal point
|
||||
while (used[nextIndex]) {
|
||||
long key = keys[nextIndex];
|
||||
int targetIndex = (int) (spread(key) & mask);
|
||||
|
||||
// If the key's ideal position is between the removal point and the current position,
|
||||
// move it to the removal point
|
||||
if ((targetIndex <= currentIndex && currentIndex < nextIndex) ||
|
||||
(nextIndex < targetIndex && targetIndex <= currentIndex) ||
|
||||
(currentIndex < nextIndex && nextIndex < targetIndex)) {
|
||||
|
||||
keys[currentIndex] = keys[nextIndex];
|
||||
used[currentIndex] = true;
|
||||
used[nextIndex] = false;
|
||||
currentIndex = nextIndex;
|
||||
}
|
||||
|
||||
nextIndex = (nextIndex + 1) & mask;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
lock.lock();
|
||||
try {
|
||||
int hash = 0;
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
if (used[i]) {
|
||||
hash += Long.hashCode(keys[i]);
|
||||
}
|
||||
}
|
||||
return hash;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Concurrent iterator for the set.
|
||||
*/
|
||||
private class ConcurrentLongIterator implements LongIterator {
|
||||
private int segmentIndex;
|
||||
private int keyIndex;
|
||||
private long lastReturned;
|
||||
private boolean lastReturnedValid;
|
||||
|
||||
ConcurrentLongIterator() {
|
||||
segmentIndex = 0;
|
||||
keyIndex = 0;
|
||||
lastReturnedValid = false;
|
||||
advance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return segmentIndex < segments.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long nextLong() {
|
||||
if (!hasNext()) {
|
||||
throw new java.util.NoSuchElementException();
|
||||
}
|
||||
|
||||
lastReturned = segments[segmentIndex].keys[keyIndex];
|
||||
lastReturnedValid = true;
|
||||
advance();
|
||||
return lastReturned;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long next() {
|
||||
return nextLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
if (!lastReturnedValid) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
ConcurrentLongHashSet.this.remove(lastReturned);
|
||||
lastReturnedValid = false;
|
||||
}
|
||||
|
||||
private void advance() {
|
||||
while (segmentIndex < segments.length) {
|
||||
Segment segment = segments[segmentIndex];
|
||||
|
||||
// Lock the segment to get a consistent view
|
||||
segment.lock.lock();
|
||||
try {
|
||||
while (keyIndex < segment.keys.length) {
|
||||
if (segment.used[keyIndex]) {
|
||||
// Found next element
|
||||
return;
|
||||
}
|
||||
keyIndex++;
|
||||
}
|
||||
} finally {
|
||||
segment.lock.unlock();
|
||||
}
|
||||
|
||||
// Move to next segment
|
||||
segmentIndex++;
|
||||
keyIndex = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility class for math operations.
|
||||
*/
|
||||
private static class MathUtil {
|
||||
/**
|
||||
* Returns the next power of two greater than or equal to the given value.
|
||||
*/
|
||||
static int nextPowerOfTwo(int value) {
|
||||
int highestBit = Integer.highestOneBit(value);
|
||||
return value > highestBit ? highestBit << 1 : value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package org.dreeam.leaf.util.map;
|
||||
|
||||
import com.google.common.collect.Interner;
|
||||
import com.google.common.collect.Interners;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectIterator;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class StringCanonizingOpenHashMap<T> extends Object2ObjectOpenHashMap<String, T> {
|
||||
|
||||
private static final Interner<String> KEY_INTERNER = Interners.newWeakInterner();
|
||||
|
||||
private static String intern(String key) {
|
||||
return key != null ? KEY_INTERNER.intern(key) : null;
|
||||
}
|
||||
|
||||
public StringCanonizingOpenHashMap() {
|
||||
super();
|
||||
}
|
||||
|
||||
public StringCanonizingOpenHashMap(int expectedSize) {
|
||||
super(expectedSize);
|
||||
}
|
||||
|
||||
public StringCanonizingOpenHashMap(int expectedSize, float loadFactor) {
|
||||
super(expectedSize, loadFactor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T put(String key, T value) {
|
||||
return super.put(intern(key), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(Map<? extends String, ? extends T> m) {
|
||||
if (m.isEmpty()) return;
|
||||
Map<String, T> tmp = new Object2ObjectOpenHashMap<>(m.size());
|
||||
m.forEach((k, v) -> tmp.put(intern(k), v));
|
||||
super.putAll(tmp);
|
||||
}
|
||||
|
||||
private void putWithoutInterning(String key, T value) {
|
||||
super.put(key, value);
|
||||
}
|
||||
|
||||
public static <T> StringCanonizingOpenHashMap<T> deepCopy(StringCanonizingOpenHashMap<T> incomingMap, Function<T, T> deepCopier) {
|
||||
StringCanonizingOpenHashMap<T> newMap = new StringCanonizingOpenHashMap<>(incomingMap.size(), 0.8f);
|
||||
ObjectIterator<Entry<String, T>> iterator = incomingMap.object2ObjectEntrySet().fastIterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<String, T> entry = iterator.next();
|
||||
newMap.putWithoutInterning(entry.getKey(), deepCopier.apply(entry.getValue()));
|
||||
}
|
||||
|
||||
return newMap;
|
||||
}
|
||||
}
|
||||
@@ -1,171 +0,0 @@
|
||||
package org.dreeam.leaf.util.math.random;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.level.levelgen.BitRandomSource;
|
||||
import net.minecraft.world.level.levelgen.PositionalRandomFactory;
|
||||
import org.dreeam.leaf.config.modules.opt.FastRNG;
|
||||
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.random.RandomGenerator;
|
||||
import java.util.random.RandomGeneratorFactory;
|
||||
|
||||
|
||||
public class FasterRandomSource implements BitRandomSource {
|
||||
|
||||
private static final int INT_BITS = 48;
|
||||
private static final long SEED_MASK = 0xFFFFFFFFFFFFL;
|
||||
private static final long MULTIPLIER = 25214903917L;
|
||||
private static final long INCREMENT = 11L;
|
||||
private static final RandomGeneratorFactory<RandomGenerator> RANDOM_GENERATOR_FACTORY = RandomGeneratorFactory.of(FastRNG.randomGenerator);
|
||||
private static final boolean isSplittableGenerator = RANDOM_GENERATOR_FACTORY.isSplittable();
|
||||
private long seed;
|
||||
private boolean useDirectImpl;
|
||||
private RandomGenerator randomGenerator;
|
||||
public static final FasterRandomSource SHARED_INSTANCE = new FasterRandomSource(ThreadLocalRandom.current().nextLong());
|
||||
|
||||
public FasterRandomSource(long seed) {
|
||||
this.seed = seed;
|
||||
this.randomGenerator = RANDOM_GENERATOR_FACTORY.create(seed);
|
||||
this.useDirectImpl = FastRNG.useDirectImpl; // Get the value from config
|
||||
}
|
||||
|
||||
private FasterRandomSource(long seed, RandomGenerator.SplittableGenerator randomGenerator) {
|
||||
this.seed = seed;
|
||||
this.randomGenerator = randomGenerator;
|
||||
this.useDirectImpl = FastRNG.useDirectImpl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final RandomSource fork() {
|
||||
if (isSplittableGenerator) {
|
||||
return new FasterRandomSource(seed, ((RandomGenerator.SplittableGenerator) this.randomGenerator).split());
|
||||
}
|
||||
return new FasterRandomSource(this.nextLong());
|
||||
}
|
||||
|
||||
@Override
|
||||
public final PositionalRandomFactory forkPositional() {
|
||||
return new FasterRandomSourcePositionalRandomFactory(this.seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void setSeed(long seed) {
|
||||
this.seed = seed;
|
||||
this.randomGenerator = RANDOM_GENERATOR_FACTORY.create(seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int next(int bits) {
|
||||
if (useDirectImpl) {
|
||||
// Direct
|
||||
return (int) ((seed = seed * MULTIPLIER + INCREMENT & SEED_MASK) >>> (INT_BITS - bits));
|
||||
} else {
|
||||
// old
|
||||
return (int) ((seed * MULTIPLIER + INCREMENT & SEED_MASK) >>> (INT_BITS - bits));
|
||||
}
|
||||
}
|
||||
|
||||
public static class FasterRandomSourcePositionalRandomFactory implements PositionalRandomFactory {
|
||||
private final long seed;
|
||||
|
||||
public FasterRandomSourcePositionalRandomFactory(long seed) {
|
||||
this.seed = seed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RandomSource at(int x, int y, int z) {
|
||||
long l = Mth.getSeed(x, y, z);
|
||||
long m = l ^ this.seed;
|
||||
return new FasterRandomSource(m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RandomSource fromHashOf(String seed) {
|
||||
int i = seed.hashCode();
|
||||
return new FasterRandomSource((long) i ^ this.seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RandomSource fromSeed(long seed) {
|
||||
return new FasterRandomSource(seed);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@Override
|
||||
public void parityConfigString(StringBuilder info) {
|
||||
info.append("FasterRandomSourcePositionalRandomFactory{").append(this.seed).append("}");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int nextInt() {
|
||||
if (useDirectImpl) {
|
||||
return (int) (((seed = seed * MULTIPLIER + INCREMENT & SEED_MASK) >>> 16) ^
|
||||
((seed = seed * MULTIPLIER + INCREMENT & SEED_MASK) >>> 32));
|
||||
} else {
|
||||
return randomGenerator.nextInt();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int nextInt(int bound) {
|
||||
if (useDirectImpl && bound > 0) {
|
||||
if ((bound & -bound) == bound) {
|
||||
return (int) ((bound * (long) next(31)) >> 31);
|
||||
}
|
||||
int bits, val;
|
||||
do {
|
||||
bits = next(31);
|
||||
val = bits % bound;
|
||||
} while (bits - val + (bound - 1) < 0);
|
||||
return val;
|
||||
} else {
|
||||
return randomGenerator.nextInt(bound);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final long nextLong() {
|
||||
if (useDirectImpl) {
|
||||
return ((long) next(32) << 32) + next(32);
|
||||
} else {
|
||||
return randomGenerator.nextLong();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean nextBoolean() {
|
||||
if (useDirectImpl) {
|
||||
return next(1) != 0;
|
||||
} else {
|
||||
return randomGenerator.nextBoolean();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final float nextFloat() {
|
||||
if (useDirectImpl) {
|
||||
return next(24) / ((float) (1 << 24));
|
||||
} else {
|
||||
return randomGenerator.nextFloat();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final double nextDouble() {
|
||||
if (useDirectImpl) {
|
||||
return (((long) next(26) << 27) + next(27)) / (double) (1L << 53);
|
||||
} else {
|
||||
return randomGenerator.nextDouble();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final double nextGaussian() {
|
||||
// delegate Gaussian distribution to RandomGenerator
|
||||
// as direct implementation would be complex (i aint doin allat)
|
||||
return randomGenerator.nextGaussian();
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package org.dreeam.leaf.version;
|
||||
|
||||
import org.galemc.gale.version.AbstractPaperVersionFetcher;
|
||||
|
||||
public class LeafVersionFetcher extends AbstractPaperVersionFetcher {
|
||||
|
||||
public LeafVersionFetcher() {
|
||||
super(
|
||||
"ver/1.21.3",
|
||||
"https://github.com/Winds-Studio/Leaf",
|
||||
"Winds-Studio",
|
||||
"Leaf",
|
||||
"Winds-Studio",
|
||||
"Leaf"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
// Gale - Gale commands - /gale command
|
||||
|
||||
package org.galemc.gale.command;
|
||||
|
||||
import io.papermc.paper.command.CommandUtil;
|
||||
import it.unimi.dsi.fastutil.Pair;
|
||||
import net.minecraft.Util;
|
||||
import org.galemc.gale.command.subcommands.InfoCommand;
|
||||
import org.galemc.gale.command.subcommands.ReloadCommand;
|
||||
import org.galemc.gale.command.subcommands.VersionCommand;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.permissions.Permission;
|
||||
import org.bukkit.permissions.PermissionDefault;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static net.kyori.adventure.text.Component.newline;
|
||||
import static net.kyori.adventure.text.Component.text;
|
||||
import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
|
||||
import static net.kyori.adventure.text.format.NamedTextColor.RED;
|
||||
|
||||
public final class GaleCommand extends Command {
|
||||
public static final String COMMAND_LABEL = "gale";
|
||||
public static final String BASE_PERM = GaleCommands.COMMAND_BASE_PERM + "." + COMMAND_LABEL;
|
||||
private static final Permission basePermission = new Permission(BASE_PERM, PermissionDefault.TRUE);
|
||||
// subcommand label -> subcommand
|
||||
private static final GaleSubcommand RELOAD_SUBCOMMAND = new ReloadCommand();
|
||||
private static final GaleSubcommand VERSION_SUBCOMMAND = new VersionCommand();
|
||||
private static final GaleSubcommand INFO_SUBCOMMAND = new InfoCommand();
|
||||
private static final Map<String, GaleSubcommand> SUBCOMMANDS = Util.make(() -> {
|
||||
final Map<Set<String>, GaleSubcommand> commands = new HashMap<>();
|
||||
|
||||
commands.put(Set.of(ReloadCommand.LITERAL_ARGUMENT), RELOAD_SUBCOMMAND);
|
||||
commands.put(Set.of(VersionCommand.LITERAL_ARGUMENT), VERSION_SUBCOMMAND);
|
||||
commands.put(Set.of(InfoCommand.LITERAL_ARGUMENT), INFO_SUBCOMMAND);
|
||||
|
||||
return commands.entrySet().stream()
|
||||
.flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue())))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
});
|
||||
// alias -> subcommand label
|
||||
private static final Map<String, String> ALIASES = Util.make(() -> {
|
||||
final Map<String, Set<String>> aliases = new HashMap<>();
|
||||
|
||||
aliases.put(VersionCommand.LITERAL_ARGUMENT, Set.of("ver"));
|
||||
aliases.put(InfoCommand.LITERAL_ARGUMENT, Set.of("about"));
|
||||
|
||||
return aliases.entrySet().stream()
|
||||
.flatMap(entry -> entry.getValue().stream().map(s -> Map.entry(s, entry.getKey())))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
});
|
||||
|
||||
private String createUsageMessage(Collection<String> arguments) {
|
||||
return "/" + COMMAND_LABEL + " [" + String.join(" | ", arguments) + "]";
|
||||
}
|
||||
|
||||
public GaleCommand() {
|
||||
super(COMMAND_LABEL);
|
||||
this.description = "Gale related commands";
|
||||
this.usageMessage = this.createUsageMessage(SUBCOMMANDS.keySet());
|
||||
final List<Permission> permissions = SUBCOMMANDS.values().stream().map(GaleSubcommand::getPermission).filter(Objects::nonNull).toList();
|
||||
this.setPermission(BASE_PERM);
|
||||
final PluginManager pluginManager = Bukkit.getServer().getPluginManager();
|
||||
pluginManager.addPermission(basePermission);
|
||||
for (final Permission permission : permissions) {
|
||||
pluginManager.addPermission(permission);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> tabComplete(
|
||||
final CommandSender sender,
|
||||
final String alias,
|
||||
final String[] args,
|
||||
final @Nullable Location location
|
||||
) throws IllegalArgumentException {
|
||||
if (args.length <= 1) {
|
||||
List<String> subCommandArguments = new ArrayList<>(SUBCOMMANDS.size());
|
||||
for (Map.Entry<String, GaleSubcommand> subCommandEntry : SUBCOMMANDS.entrySet()) {
|
||||
if (subCommandEntry.getValue().testPermission(sender)) {
|
||||
subCommandArguments.add(subCommandEntry.getKey());
|
||||
}
|
||||
}
|
||||
return CommandUtil.getListMatchingLast(sender, args, subCommandArguments);
|
||||
}
|
||||
|
||||
final @Nullable Pair<String, GaleSubcommand> subCommand = resolveCommand(args[0]);
|
||||
if (subCommand != null && subCommand.second().testPermission(sender)) {
|
||||
return subCommand.second().tabComplete(sender, subCommand.first(), Arrays.copyOfRange(args, 1, args.length));
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private boolean testHasOnePermission(CommandSender sender) {
|
||||
for (Map.Entry<String, GaleSubcommand> subCommandEntry : SUBCOMMANDS.entrySet()) {
|
||||
if (subCommandEntry.getValue().testPermission(sender)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute(
|
||||
final CommandSender sender,
|
||||
final String commandLabel,
|
||||
final String[] args
|
||||
) {
|
||||
|
||||
// Check if the sender has the base permission and at least one specific permission
|
||||
if (!sender.hasPermission(basePermission) || !this.testHasOnePermission(sender)) {
|
||||
sender.sendMessage(Bukkit.permissionMessage());
|
||||
return true;
|
||||
}
|
||||
|
||||
// Determine the usage message with the subcommands they can perform
|
||||
List<String> subCommandArguments = new ArrayList<>(SUBCOMMANDS.size());
|
||||
for (Map.Entry<String, GaleSubcommand> subCommandEntry : SUBCOMMANDS.entrySet()) {
|
||||
if (subCommandEntry.getValue().testPermission(sender)) {
|
||||
subCommandArguments.add(subCommandEntry.getKey());
|
||||
}
|
||||
}
|
||||
String specificUsageMessage = this.createUsageMessage(subCommandArguments);
|
||||
|
||||
// If they did not give a subcommand
|
||||
if (args.length == 0) {
|
||||
INFO_SUBCOMMAND.execute(sender, InfoCommand.LITERAL_ARGUMENT, me.titaniumtown.ArrayConstants.emptyStringArray); // Gale - JettPack - reduce array allocations
|
||||
sender.sendMessage(newline().append(text("Command usage: " + specificUsageMessage, GRAY)));
|
||||
return false;
|
||||
}
|
||||
|
||||
// If they do not have permission for the subcommand they gave, or the argument is not a valid subcommand
|
||||
final @Nullable Pair<String, GaleSubcommand> subCommand = resolveCommand(args[0]);
|
||||
if (subCommand == null || !subCommand.second().testPermission(sender)) {
|
||||
sender.sendMessage(text("Usage: " + specificUsageMessage, RED));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Execute the subcommand
|
||||
final String[] choppedArgs = Arrays.copyOfRange(args, 1, args.length);
|
||||
return subCommand.second().execute(sender, subCommand.first(), choppedArgs);
|
||||
|
||||
}
|
||||
|
||||
private static @Nullable Pair<String, GaleSubcommand> resolveCommand(String label) {
|
||||
label = label.toLowerCase(Locale.ENGLISH);
|
||||
@Nullable GaleSubcommand subCommand = SUBCOMMANDS.get(label);
|
||||
if (subCommand == null) {
|
||||
final @Nullable String command = ALIASES.get(label);
|
||||
if (command != null) {
|
||||
label = command;
|
||||
subCommand = SUBCOMMANDS.get(command);
|
||||
}
|
||||
}
|
||||
|
||||
if (subCommand != null) {
|
||||
return Pair.of(label, subCommand);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
// Gale - Gale commands
|
||||
|
||||
package org.galemc.gale.command;
|
||||
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.framework.qual.DefaultQualifier;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.craftbukkit.util.permissions.CraftDefaultPermissions;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@DefaultQualifier(NonNull.class)
|
||||
public final class GaleCommands {
|
||||
|
||||
public static final String COMMAND_BASE_PERM = CraftDefaultPermissions.GALE_ROOT + ".command";
|
||||
|
||||
private GaleCommands() {
|
||||
}
|
||||
|
||||
private static final Map<String, Command> COMMANDS = new HashMap<>();
|
||||
|
||||
static {
|
||||
COMMANDS.put(GaleCommand.COMMAND_LABEL, new GaleCommand());
|
||||
}
|
||||
|
||||
public static void registerCommands(final MinecraftServer server) {
|
||||
COMMANDS.forEach((s, command) ->
|
||||
server.server.getCommandMap().register(s, "Gale", command)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
// Gale - Gale commands
|
||||
|
||||
package org.galemc.gale.command;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.framework.qual.DefaultQualifier;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.permissions.Permission;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@DefaultQualifier(NonNull.class)
|
||||
public interface GaleSubcommand {
|
||||
|
||||
boolean execute(CommandSender sender, String subCommand, String[] args);
|
||||
|
||||
default List<String> tabComplete(final CommandSender sender, final String subCommand, final String[] args) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
boolean testPermission(CommandSender sender);
|
||||
|
||||
@Nullable Permission getPermission();
|
||||
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
// Gale - Gale commands
|
||||
|
||||
package org.galemc.gale.command;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.permissions.Permission;
|
||||
import org.bukkit.permissions.PermissionDefault;
|
||||
|
||||
public abstract class PermissionedGaleSubcommand implements GaleSubcommand {
|
||||
|
||||
public final Permission permission;
|
||||
|
||||
protected PermissionedGaleSubcommand(Permission permission) {
|
||||
this.permission = permission;
|
||||
}
|
||||
|
||||
protected PermissionedGaleSubcommand(String permission, PermissionDefault permissionDefault) {
|
||||
this(new Permission(permission, permissionDefault));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean testPermission(CommandSender sender) {
|
||||
return sender.hasPermission(this.permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Permission getPermission() {
|
||||
return this.permission;
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user