mirror of
https://github.com/Winds-Studio/Leaf.git
synced 2025-12-25 18:09:17 +00:00
Apply some src
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
package gg.pufferfish.pufferfish.sentry;
|
||||
|
||||
import com.google.common.reflect.TypeToken;
|
||||
import com.google.gson.Gson;
|
||||
import io.sentry.Breadcrumb;
|
||||
import io.sentry.Sentry;
|
||||
import io.sentry.SentryEvent;
|
||||
import io.sentry.SentryLevel;
|
||||
import io.sentry.protocol.Message;
|
||||
import io.sentry.protocol.User;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package gg.pufferfish.pufferfish.sentry;
|
||||
|
||||
import io.sentry.Sentry;
|
||||
import org.apache.logging.log4j.Level;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
public class SentryManager {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(SentryManager.class);
|
||||
|
||||
private SentryManager() {
|
||||
|
||||
}
|
||||
|
||||
private static boolean initialized = false;
|
||||
|
||||
public static synchronized void init(Level logLevel) {
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
if (logLevel == null) {
|
||||
LOGGER.error("Invalid log level, defaulting to WARN.");
|
||||
logLevel = Level.WARN;
|
||||
}
|
||||
try {
|
||||
initialized = true;
|
||||
|
||||
Sentry.init(options -> {
|
||||
options.setDsn(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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// 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];
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
17
leaf-server/src/main/java/org/dreeam/leaf/LeafBootstrap.java
Normal file
17
leaf-server/src/main/java/org/dreeam/leaf/LeafBootstrap.java
Normal file
@@ -0,0 +1,17 @@
|
||||
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() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
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();
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
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() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
292
leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java
Normal file
292
leaf-server/src/main/java/org/dreeam/leaf/config/LeafConfig.java
Normal file
@@ -0,0 +1,292 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.dreeam.leaf.config.annotations;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface DoNotLoad {
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
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 {
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.dreeam.leaf.config.annotations;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface HotReloadUnsupported {
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
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 让玩家进入已满的服务器."""));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
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 配置里"""));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
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 漏洞!!"""));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
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.",
|
||||
"使玩家可以击退僵尸."
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
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",
|
||||
"是否只允许玩家被实体推动"
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
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.""",
|
||||
"""
|
||||
**实验性功能**
|
||||
是否在玩家切换世界时尝试使用 "平滑传送".
|
||||
此项要求源世界和目标世界逻辑高度相同才会生效."""
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
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.",
|
||||
"刷怪笼生成怪物之间的最大延迟 (以刻为单位). 较高的值会减缓刷怪笼的速度."
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
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.",
|
||||
"缓存过期时间. 单位: 分钟."
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
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>");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
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.",
|
||||
"移除修改无法编辑的告示牌时输出的警告."
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
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 模式即可让玩家加入后端服务器."""));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
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.""",
|
||||
"""
|
||||
移除原版的用户名验证,
|
||||
让所有字符均可作为玩家名."""));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
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>, 显示未知命令详细信息."""));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
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.
|
||||
""",
|
||||
"""
|
||||
是否启用聊天签名, 禁用后玩家无法进行聊天举报."""));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.dreeam.leaf.config.modules.network;
|
||||
|
||||
import org.dreeam.leaf.config.ConfigModules;
|
||||
import org.dreeam.leaf.config.EnumConfigCategory;
|
||||
|
||||
public class OptimizeNonFlushPacketSending extends ConfigModules {
|
||||
|
||||
public String getBasePath() {
|
||||
return EnumConfigCategory.NETWORK.getBaseKeyName();
|
||||
}
|
||||
|
||||
public static boolean enabled = false;
|
||||
|
||||
@Override
|
||||
public void onLoaded() {
|
||||
enabled = config.getBoolean(getBasePath() + ".OptimizeNonFlushPacketSending", enabled, config.pickStringRegionBased("""
|
||||
WARNING: This option is NOT compatible with ProtocolLib and may cause
|
||||
issues with other plugins that modify packet handling.
|
||||
|
||||
Optimizes non-flush packet sending by using Netty's lazyExecute method to avoid
|
||||
expensive thread wakeup calls when scheduling packet operations.
|
||||
|
||||
Requires server restart to take effect.
|
||||
""",
|
||||
"""
|
||||
警告: 此选项与 ProtocolLib 不兼容, 并可能导致与其他修改数据包
|
||||
处理的插件出现问题.
|
||||
|
||||
通过使用 Netty 的 lazyExecute 方法来优化非刷新数据包的发送,
|
||||
避免在调度数据包操作时进行昂贵的线程唤醒调用.
|
||||
|
||||
需要重启服务器才能生效.
|
||||
"""));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
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 列表缓存的间隔."));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
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")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
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的实体类型转换."""));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
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.",
|
||||
"更快的结构生成任务分段."));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
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."""));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
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 目标选择器.
|
||||
有助于提升性能, 但对游戏有轻微影响."""));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
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.",
|
||||
"是否为异步任务调度器使用虚拟线程."));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
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.",
|
||||
"是否为异步聊天线程使用虚拟线程."));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
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.",
|
||||
"是否为用户验证器使用虚拟线程."));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
23
leaf-server/src/main/java/org/dreeam/leaf/util/cache/CachedOrNewBitsGetter.java
vendored
Normal file
23
leaf-server/src/main/java/org/dreeam/leaf/util/cache/CachedOrNewBitsGetter.java
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
74
leaf-server/src/main/java/org/dreeam/leaf/util/cache/IterateOutwardsCache.java
vendored
Normal file
74
leaf-server/src/main/java/org/dreeam/leaf/util/cache/IterateOutwardsCache.java
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
47
leaf-server/src/main/java/org/dreeam/leaf/util/cache/LongList2BlockPosMutableIterable.java
vendored
Normal file
47
leaf-server/src/main/java/org/dreeam/leaf/util/cache/LongList2BlockPosMutableIterable.java
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
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));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,695 @@
|
||||
package org.dreeam.leaf.util.map;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
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 {
|
||||
Arrays.fill(used, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
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));
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return randomGenerator.nextInt(bound);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final long nextLong() {
|
||||
if (useDirectImpl) {
|
||||
return ((long) next(32) << 32) + next(32);
|
||||
}
|
||||
|
||||
return randomGenerator.nextLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean nextBoolean() {
|
||||
if (useDirectImpl) {
|
||||
return next(1) != 0;
|
||||
}
|
||||
|
||||
return randomGenerator.nextBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final float nextFloat() {
|
||||
if (useDirectImpl) {
|
||||
return next(24) / ((float) (1 << 24));
|
||||
}
|
||||
|
||||
return randomGenerator.nextFloat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final double nextDouble() {
|
||||
if (useDirectImpl) {
|
||||
return (((long) next(26) << 27) + next(27)) / (double) (1L << 53);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
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"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
// Gale - Gale configuration
|
||||
|
||||
package org.galemc.gale.configuration;
|
||||
|
||||
import com.google.common.collect.Table;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import io.leangen.geantyref.TypeToken;
|
||||
import io.papermc.paper.configuration.Configuration;
|
||||
import io.papermc.paper.configuration.ConfigurationPart;
|
||||
import io.papermc.paper.configuration.Configurations;
|
||||
import io.papermc.paper.configuration.NestedSetting;
|
||||
import io.papermc.paper.configuration.PaperConfigurations;
|
||||
import io.papermc.paper.configuration.legacy.RequiresSpigotInitialization;
|
||||
import io.papermc.paper.configuration.mapping.InnerClassFieldDiscoverer;
|
||||
import io.papermc.paper.configuration.serializer.ComponentSerializer;
|
||||
import io.papermc.paper.configuration.serializer.EnumValueSerializer;
|
||||
import io.papermc.paper.configuration.serializer.PacketClassSerializer;
|
||||
import io.papermc.paper.configuration.serializer.StringRepresentableSerializer;
|
||||
import io.papermc.paper.configuration.serializer.collections.FastutilMapSerializer;
|
||||
import io.papermc.paper.configuration.serializer.collections.MapSerializer;
|
||||
import io.papermc.paper.configuration.serializer.collections.TableSerializer;
|
||||
import io.papermc.paper.configuration.serializer.registry.RegistryHolderSerializer;
|
||||
import io.papermc.paper.configuration.serializer.registry.RegistryValueSerializer;
|
||||
import io.papermc.paper.configuration.transformation.Transformations;
|
||||
import io.papermc.paper.configuration.type.BooleanOrDefault;
|
||||
import io.papermc.paper.configuration.type.Duration;
|
||||
import io.papermc.paper.configuration.type.EngineMode;
|
||||
import io.papermc.paper.configuration.type.fallback.FallbackValueSerializer;
|
||||
import io.papermc.paper.configuration.type.number.DoubleOr;
|
||||
import io.papermc.paper.configuration.type.number.IntOr;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2LongMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2LongOpenHashMap;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.core.component.DataComponentType;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.spongepowered.configurate.ConfigurateException;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.ConfigurationOptions;
|
||||
import org.spongepowered.configurate.NodePath;
|
||||
import org.spongepowered.configurate.objectmapping.ObjectMapper;
|
||||
import org.spongepowered.configurate.transformation.ConfigurationTransformation;
|
||||
import org.spongepowered.configurate.transformation.TransformAction;
|
||||
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static io.leangen.geantyref.GenericTypeReflector.erase;
|
||||
|
||||
@SuppressWarnings("Convert2Diamond")
|
||||
public class GaleConfigurations extends Configurations<GaleGlobalConfiguration, GaleWorldConfiguration> {
|
||||
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
static final String GLOBAL_CONFIG_FILE_NAME = "gale-global.yml";
|
||||
static final String WORLD_DEFAULTS_CONFIG_FILE_NAME = "gale-world-defaults.yml";
|
||||
static final String WORLD_CONFIG_FILE_NAME = "gale-world.yml";
|
||||
public static final String CONFIG_DIR = "config";
|
||||
|
||||
private static final String GLOBAL_HEADER = String.format("""
|
||||
This is the global configuration file for Gale.
|
||||
As you can see, there's a lot to configure. Some options may impact gameplay, so use
|
||||
with caution, and make sure you know what each option does before configuring.
|
||||
|
||||
If you need help with the configuration or have any questions related to Gale,
|
||||
join us in our Discord, or check the GitHub Wiki pages.
|
||||
|
||||
The world configuration options are inside
|
||||
their respective world folder. The files are named %s
|
||||
|
||||
Wiki: https://github.com/GaleMC/Gale/wiki
|
||||
Discord: https://discord.gg/gwezNT8c24""", WORLD_CONFIG_FILE_NAME);
|
||||
|
||||
private static final String WORLD_DEFAULTS_HEADER = """
|
||||
This is the world defaults configuration file for Gale.
|
||||
As you can see, there's a lot to configure. Some options may impact gameplay, so use
|
||||
with caution, and make sure you know what each option does before configuring.
|
||||
|
||||
If you need help with the configuration or have any questions related to Gale,
|
||||
join us in our Discord, or check the GitHub Wiki pages.
|
||||
|
||||
Configuration options here apply to all worlds, unless you specify overrides inside
|
||||
the world-specific config file inside each world folder.
|
||||
|
||||
Wiki: https://github.com/GaleMC/Gale/wiki
|
||||
Discord: https://discord.gg/gwezNT8c24""";
|
||||
|
||||
private static final Function<ContextMap, String> WORLD_HEADER = map -> String.format("""
|
||||
This is a world configuration file for Gale.
|
||||
This file may start empty but can be filled with settings to override ones in the %s/%s
|
||||
|
||||
World: %s (%s)""",
|
||||
CONFIG_DIR,
|
||||
WORLD_DEFAULTS_CONFIG_FILE_NAME,
|
||||
map.require(WORLD_NAME),
|
||||
map.require(WORLD_KEY)
|
||||
);
|
||||
|
||||
private static final String MOVED_NOTICE = """
|
||||
The global and world default configuration files have moved to %s
|
||||
and the world-specific configuration file has been moved inside
|
||||
the respective world folder.
|
||||
|
||||
See https://github.com/GaleMC/Gale/wiki for more information.
|
||||
""";
|
||||
|
||||
public GaleConfigurations(final Path globalFolder) {
|
||||
super(globalFolder, GaleGlobalConfiguration.class, GaleWorldConfiguration.class, GLOBAL_CONFIG_FILE_NAME, WORLD_DEFAULTS_CONFIG_FILE_NAME, WORLD_CONFIG_FILE_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected YamlConfigurationLoader.Builder createLoaderBuilder() {
|
||||
return super.createLoaderBuilder()
|
||||
.defaultOptions(GaleConfigurations::defaultOptions);
|
||||
}
|
||||
|
||||
private static ConfigurationOptions defaultOptions(ConfigurationOptions options) {
|
||||
return options.serializers(builder -> builder
|
||||
.register(MapSerializer.TYPE, new MapSerializer(false))
|
||||
.register(new EnumValueSerializer())
|
||||
.register(new ComponentSerializer())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ObjectMapper.Factory.Builder createGlobalObjectMapperFactoryBuilder() {
|
||||
return defaultGlobalFactoryBuilder(super.createGlobalObjectMapperFactoryBuilder());
|
||||
}
|
||||
|
||||
private static ObjectMapper.Factory.Builder defaultGlobalFactoryBuilder(ObjectMapper.Factory.Builder builder) {
|
||||
return builder.addDiscoverer(InnerClassFieldDiscoverer.globalConfig());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected YamlConfigurationLoader.Builder createGlobalLoaderBuilder(RegistryAccess registryAccess) {
|
||||
return super.createGlobalLoaderBuilder(registryAccess)
|
||||
.defaultOptions((options) -> defaultGlobalOptions(registryAccess, options));
|
||||
}
|
||||
|
||||
private static ConfigurationOptions defaultGlobalOptions(RegistryAccess registryAccess, ConfigurationOptions options) {
|
||||
return options
|
||||
.header(GLOBAL_HEADER)
|
||||
.serializers(builder -> builder.register(new PacketClassSerializer())
|
||||
.register(new RegistryValueSerializer<>(new TypeToken<DataComponentType<?>>() {}, registryAccess, Registries.DATA_COMPONENT_TYPE, false))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GaleGlobalConfiguration initializeGlobalConfiguration(final RegistryAccess registryAccess) throws ConfigurateException {
|
||||
GaleGlobalConfiguration configuration = super.initializeGlobalConfiguration(registryAccess);
|
||||
GaleGlobalConfiguration.set(configuration);
|
||||
return configuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ContextMap.Builder createDefaultContextMap(final RegistryAccess registryAccess) {
|
||||
return super.createDefaultContextMap(registryAccess)
|
||||
.put(PaperConfigurations.SPIGOT_WORLD_CONFIG_CONTEXT_KEY, PaperConfigurations.SPIGOT_WORLD_DEFAULTS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ObjectMapper.Factory.Builder createWorldObjectMapperFactoryBuilder(final ContextMap contextMap) {
|
||||
return super.createWorldObjectMapperFactoryBuilder(contextMap)
|
||||
.addNodeResolver(new RequiresSpigotInitialization.Factory(contextMap.require(PaperConfigurations.SPIGOT_WORLD_CONFIG_CONTEXT_KEY).get()))
|
||||
.addNodeResolver(new NestedSetting.Factory())
|
||||
.addDiscoverer(InnerClassFieldDiscoverer.galeWorldConfig(contextMap));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected YamlConfigurationLoader.Builder createWorldConfigLoaderBuilder(final ContextMap contextMap) {
|
||||
final RegistryAccess access = contextMap.require(REGISTRY_ACCESS);
|
||||
return super.createWorldConfigLoaderBuilder(contextMap)
|
||||
.defaultOptions(options -> options
|
||||
.header(contextMap.require(WORLD_NAME).equals(WORLD_DEFAULTS) ? WORLD_DEFAULTS_HEADER : WORLD_HEADER.apply(contextMap))
|
||||
.serializers(serializers -> serializers
|
||||
.register(new TypeToken<Reference2IntMap<?>>() {}, new FastutilMapSerializer.SomethingToPrimitive<Reference2IntMap<?>>(Reference2IntOpenHashMap::new, Integer.TYPE))
|
||||
.register(new TypeToken<Reference2LongMap<?>>() {}, new FastutilMapSerializer.SomethingToPrimitive<Reference2LongMap<?>>(Reference2LongOpenHashMap::new, Long.TYPE))
|
||||
.register(new TypeToken<Table<?, ?, ?>>() {}, new TableSerializer())
|
||||
.register(new StringRepresentableSerializer())
|
||||
.register(IntOr.Default.SERIALIZER)
|
||||
.register(IntOr.Disabled.SERIALIZER)
|
||||
.register(DoubleOr.Default.SERIALIZER)
|
||||
.register(BooleanOrDefault.SERIALIZER)
|
||||
.register(Duration.SERIALIZER)
|
||||
.register(EngineMode.SERIALIZER)
|
||||
.register(FallbackValueSerializer.create(contextMap.require(PaperConfigurations.SPIGOT_WORLD_CONFIG_CONTEXT_KEY).get(), MinecraftServer::getServer))
|
||||
.register(new RegistryValueSerializer<>(new TypeToken<EntityType<?>>() {}, access, Registries.ENTITY_TYPE, true))
|
||||
.register(new RegistryValueSerializer<>(Item.class, access, Registries.ITEM, true))
|
||||
.register(new RegistryHolderSerializer<>(new TypeToken<ConfiguredFeature<?, ?>>() {}, access, Registries.CONFIGURED_FEATURE, false))
|
||||
.register(new RegistryHolderSerializer<>(Item.class, access, Registries.ITEM, true))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyWorldConfigTransformations(final ContextMap contextMap, final ConfigurationNode node, final @Nullable ConfigurationNode defaultsNode) throws ConfigurateException {
|
||||
final ConfigurationNode version = node.node(Configuration.VERSION_FIELD);
|
||||
final String world = contextMap.require(WORLD_NAME);
|
||||
if (version.virtual()) {
|
||||
LOGGER.warn("The Gale world config file for {} didn't have a version set, assuming latest", world);
|
||||
version.raw(GaleWorldConfiguration.CURRENT_VERSION);
|
||||
}
|
||||
if (GaleRemovedConfigurations.REMOVED_WORLD_PATHS.length > 0) {
|
||||
ConfigurationTransformation.Builder builder = ConfigurationTransformation.builder();
|
||||
for (NodePath path : GaleRemovedConfigurations.REMOVED_WORLD_PATHS) {
|
||||
builder.addAction(path, TransformAction.remove());
|
||||
}
|
||||
builder.build().apply(node);
|
||||
}
|
||||
// ADD FUTURE TRANSFORMS HERE
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyGlobalConfigTransformations(ConfigurationNode node) throws ConfigurateException {
|
||||
if (GaleRemovedConfigurations.REMOVED_GLOBAL_PATHS.length > 0) {
|
||||
ConfigurationTransformation.Builder builder = ConfigurationTransformation.builder();
|
||||
for (NodePath path : GaleRemovedConfigurations.REMOVED_GLOBAL_PATHS) {
|
||||
builder.addAction(path, TransformAction.remove());
|
||||
}
|
||||
builder.build().apply(node);
|
||||
}
|
||||
// ADD FUTURE TRANSFORMS HERE
|
||||
}
|
||||
|
||||
private static final List<Transformations.DefaultsAware> DEFAULT_AWARE_TRANSFORMATIONS = Collections.emptyList();
|
||||
|
||||
@Override
|
||||
protected void applyDefaultsAwareWorldConfigTransformations(final ContextMap contextMap, final ConfigurationNode worldNode, final ConfigurationNode defaultsNode) throws ConfigurateException {
|
||||
final ConfigurationTransformation.Builder builder = ConfigurationTransformation.builder();
|
||||
// ADD FUTURE TRANSFORMS HERE (these transforms run after the defaults have been merged into the node)
|
||||
DEFAULT_AWARE_TRANSFORMATIONS.forEach(transform -> transform.apply(builder, contextMap, defaultsNode));
|
||||
|
||||
ConfigurationTransformation transformation;
|
||||
try {
|
||||
transformation = builder.build(); // build throws IAE if no actions were provided (bad zml)
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
return;
|
||||
}
|
||||
transformation.apply(worldNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GaleWorldConfiguration createWorldConfig(final ContextMap contextMap) {
|
||||
final String levelName = contextMap.require(WORLD_NAME);
|
||||
try {
|
||||
return super.createWorldConfig(contextMap);
|
||||
} catch (IOException exception) {
|
||||
throw new RuntimeException("Could not create Gale world config for " + levelName, exception);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isConfigType(final Type type) {
|
||||
return ConfigurationPart.class.isAssignableFrom(erase(type));
|
||||
}
|
||||
|
||||
public void reloadConfigs(MinecraftServer server) {
|
||||
try {
|
||||
this.initializeGlobalConfiguration(server.registryAccess(), reloader(this.globalConfigClass, GaleGlobalConfiguration.get()));
|
||||
this.initializeWorldDefaultsConfiguration(server.registryAccess());
|
||||
for (ServerLevel level : server.getAllLevels()) {
|
||||
this.createWorldConfig(PaperConfigurations.createWorldContextMap(level), reloader(this.worldConfigClass, level.galeConfig()));
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException("Could not reload Gale configuration files", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static GaleConfigurations setup(final Path configDir) throws Exception {
|
||||
try {
|
||||
PaperConfigurations.createDirectoriesSymlinkAware(configDir);
|
||||
return new GaleConfigurations(configDir);
|
||||
} catch (final IOException ex) {
|
||||
throw new RuntimeException("Could not setup GaleConfigurations", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int globalConfigVersion() {
|
||||
return GaleGlobalConfiguration.CURRENT_VERSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int worldConfigVersion() {
|
||||
return getWorldConfigurationCurrentVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWorldConfigurationCurrentVersion() {
|
||||
return GaleWorldConfiguration.CURRENT_VERSION;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
// Gale - Gale configuration
|
||||
|
||||
package org.galemc.gale.configuration;
|
||||
|
||||
import io.papermc.paper.configuration.Configuration;
|
||||
import io.papermc.paper.configuration.ConfigurationPart;
|
||||
import net.minecraft.world.level.levelgen.structure.PoolElementStructurePiece;
|
||||
import org.spongepowered.configurate.objectmapping.meta.PostProcess;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Setting;
|
||||
import org.bukkit.plugin.java.JavaPluginLoader;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@SuppressWarnings({"CanBeFinal", "FieldCanBeLocal", "FieldMayBeFinal", "InnerClassMayBeStatic"})
|
||||
public class GaleGlobalConfiguration extends ConfigurationPart {
|
||||
static final int CURRENT_VERSION = 1;
|
||||
private static GaleGlobalConfiguration instance;
|
||||
|
||||
public static GaleGlobalConfiguration get() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
static void set(GaleGlobalConfiguration instance) {
|
||||
GaleGlobalConfiguration.instance = instance;
|
||||
}
|
||||
|
||||
@Setting(Configuration.VERSION_FIELD)
|
||||
public int version = CURRENT_VERSION;
|
||||
|
||||
public SmallOptimizations smallOptimizations;
|
||||
|
||||
public class SmallOptimizations extends ConfigurationPart {
|
||||
|
||||
public ReducedIntervals reducedIntervals;
|
||||
|
||||
public class ReducedIntervals extends ConfigurationPart {
|
||||
|
||||
public int increaseTimeStatistics = 20; // Gale - Hydrinity - increase time statistics in intervals
|
||||
public int updateEntityLineOfSight = 4; // Gale - Petal - reduce line of sight updates
|
||||
|
||||
@PostProcess
|
||||
public void postProcess() {
|
||||
net.minecraft.world.entity.player.Player.increaseTimeStatisticsInterval = Math.max(1, increaseTimeStatistics); // Gale - Hydrinity - increase time statistics in intervals - store as static field for fast access
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public GameplayMechanics gameplayMechanics;
|
||||
|
||||
public class GameplayMechanics extends ConfigurationPart {
|
||||
|
||||
public boolean enableBookWriting = true; // Gale - Pufferfish - make book writing configurable
|
||||
|
||||
}
|
||||
|
||||
public Misc misc;
|
||||
|
||||
public class Misc extends ConfigurationPart {
|
||||
|
||||
public boolean verifyChatOrder = true; // Gale - Pufferfish - make chat order verification configurable
|
||||
public int premiumAccountSlowLoginTimeout = -1; // Gale - make slow login timeout configurable
|
||||
public boolean ignoreNullLegacyStructureData = false; // Gale - MultiPaper - ignore null legacy structure data
|
||||
|
||||
public Keepalive keepalive;
|
||||
|
||||
public class Keepalive extends ConfigurationPart {
|
||||
public boolean sendMultiple = true; // Gale - Purpur - send multiple keep-alive packets
|
||||
}
|
||||
|
||||
// Gale start - YAPFA - last tick time - in TPS command
|
||||
public LastTickTimeInTpsCommand lastTickTimeInTpsCommand;
|
||||
|
||||
public class LastTickTimeInTpsCommand extends ConfigurationPart {
|
||||
public boolean enabled = false;
|
||||
public boolean addOversleep = false;
|
||||
}
|
||||
// Gale end - YAPFA - last tick time - in TPS command
|
||||
|
||||
}
|
||||
|
||||
public LogToConsole logToConsole;
|
||||
|
||||
public class LogToConsole extends ConfigurationPart { // Gale - EMC - softly log invalid pool element errors
|
||||
|
||||
public boolean invalidStatistics = true; // Gale - EMC - do not log invalid statistics
|
||||
public boolean ignoredAdvancements = true; // Gale - Purpur - do not log ignored advancements
|
||||
public boolean setBlockInFarChunk = true; // Gale - Purpur - do not log setBlock in far chunks
|
||||
public boolean unrecognizedRecipes = false; // Gale - Purpur - do not log unrecognized recipes
|
||||
public boolean legacyMaterialInitialization = false; // Gale - Purpur - do not log legacy Material initialization
|
||||
public boolean nullIdDisconnections = true; // Gale - Pufferfish - do not log disconnections with null id
|
||||
public boolean playerLoginLocations = true; // Gale - JettPack - make logging login location configurable
|
||||
|
||||
public Chat chat;
|
||||
|
||||
public class Chat extends ConfigurationPart {
|
||||
public boolean emptyMessageWarning = false; // Gale - do not log empty message warnings
|
||||
public boolean expiredMessageWarning = false; // Gale - do not log expired message warnings
|
||||
public boolean notSecureMarker = true; // Gale - do not log Not Secure marker
|
||||
}
|
||||
|
||||
// Gale start - Purpur - do not log plugin library loads
|
||||
public PluginLibraryLoader pluginLibraryLoader;
|
||||
|
||||
public class PluginLibraryLoader extends ConfigurationPart {
|
||||
|
||||
public boolean downloads = true;
|
||||
public boolean startLoadLibrariesForPlugin = true;
|
||||
public boolean libraryLoaded = true;
|
||||
|
||||
@PostProcess
|
||||
public void postProcess() {
|
||||
JavaPluginLoader.logDownloads = this.downloads;
|
||||
JavaPluginLoader.logStartLoadLibrariesForPlugin = this.startLoadLibrariesForPlugin;
|
||||
JavaPluginLoader.logLibraryLoaded = this.libraryLoaded;
|
||||
}
|
||||
|
||||
}
|
||||
// Gale end - Purpur - do not log plugin library loads
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Gale - Gale configuration
|
||||
|
||||
package org.galemc.gale.configuration;
|
||||
|
||||
import org.spongepowered.configurate.NodePath;
|
||||
|
||||
interface GaleRemovedConfigurations {
|
||||
|
||||
NodePath[] REMOVED_WORLD_PATHS = {};
|
||||
|
||||
NodePath[] REMOVED_GLOBAL_PATHS = {};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
// Gale - Gale configuration
|
||||
|
||||
package org.galemc.gale.configuration;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import io.papermc.paper.configuration.Configuration;
|
||||
import io.papermc.paper.configuration.ConfigurationPart;
|
||||
import io.papermc.paper.configuration.PaperConfigurations;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.slf4j.Logger;
|
||||
import org.spigotmc.SpigotWorldConfig;
|
||||
import org.spongepowered.configurate.objectmapping.meta.Setting;
|
||||
|
||||
@SuppressWarnings({"FieldCanBeLocal", "FieldMayBeFinal", "InnerClassMayBeStatic"})
|
||||
public class GaleWorldConfiguration extends ConfigurationPart {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
public static final int CURRENT_VERSION = 1;
|
||||
|
||||
private transient final SpigotWorldConfig spigotConfig;
|
||||
private transient final ResourceLocation worldKey;
|
||||
|
||||
public GaleWorldConfiguration(SpigotWorldConfig spigotConfig, ResourceLocation worldKey) {
|
||||
this.spigotConfig = spigotConfig;
|
||||
this.worldKey = worldKey;
|
||||
}
|
||||
|
||||
public boolean isDefault() {
|
||||
return this.worldKey.equals(PaperConfigurations.WORLD_DEFAULTS_KEY);
|
||||
}
|
||||
|
||||
@Setting(Configuration.VERSION_FIELD)
|
||||
public int version = CURRENT_VERSION;
|
||||
|
||||
public SmallOptimizations smallOptimizations;
|
||||
|
||||
public class SmallOptimizations extends ConfigurationPart {
|
||||
|
||||
public boolean saveFireworks = true; // Gale - EMC - make saving fireworks configurable
|
||||
public boolean useOptimizedSheepOffspringColor = true; // Gale - carpet-fixes - optimize sheep offspring color
|
||||
|
||||
// Gale start - Airplane - reduce projectile chunk loading
|
||||
public MaxProjectileChunkLoads maxProjectileChunkLoads;
|
||||
|
||||
public class MaxProjectileChunkLoads extends ConfigurationPart {
|
||||
|
||||
public int perTick = 10;
|
||||
|
||||
public PerProjectile perProjectile;
|
||||
|
||||
public class PerProjectile extends ConfigurationPart {
|
||||
public int max = 10;
|
||||
public boolean resetMovementAfterReachLimit = false;
|
||||
public boolean removeFromWorldAfterReachLimit = false;
|
||||
}
|
||||
|
||||
}
|
||||
// Gale end - Airplane - reduce projectile chunk loading
|
||||
|
||||
public ReducedIntervals reducedIntervals;
|
||||
|
||||
public class ReducedIntervals extends ConfigurationPart {
|
||||
|
||||
public int acquirePoiForStuckEntity = 60; // Gale - Airplane - reduce acquire POI for stuck entities
|
||||
public int checkStuckInWall = 10; // Gale - Pufferfish - reduce in wall checks
|
||||
public int villagerItemRepickup = 100; // Gale - EMC - reduce villager item re-pickup
|
||||
|
||||
public CheckNearbyItem checkNearbyItem;
|
||||
|
||||
public class CheckNearbyItem extends ConfigurationPart {
|
||||
|
||||
// Gale start - EMC - reduce hopper item checks
|
||||
public Hopper hopper;
|
||||
|
||||
public class Hopper extends ConfigurationPart {
|
||||
|
||||
public int interval = 1;
|
||||
|
||||
public Minecart minecart;
|
||||
|
||||
public class Minecart extends ConfigurationPart {
|
||||
|
||||
public int interval = 1;
|
||||
|
||||
public TemporaryImmunity temporaryImmunity;
|
||||
|
||||
public class TemporaryImmunity extends ConfigurationPart {
|
||||
public int duration = 100;
|
||||
public int nearbyItemMaxAge = 1200;
|
||||
public int checkForMinecartNearItemInterval = 20;
|
||||
public boolean checkForMinecartNearItemWhileInactive = true;
|
||||
public double maxItemHorizontalDistance = 24.0;
|
||||
public double maxItemVerticalDistance = 4.0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
// Gale end - EMC - reduce hopper item checks
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public LoadChunks loadChunks;
|
||||
|
||||
public class LoadChunks extends ConfigurationPart {
|
||||
public boolean toSpawnPhantoms = false; // Gale - MultiPaper - don't load chunks to spawn phantoms
|
||||
public boolean toActivateClimbingEntities = false; // Gale - don't load chunks to activate climbing entities
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public GameplayMechanics gameplayMechanics;
|
||||
|
||||
public class GameplayMechanics extends ConfigurationPart {
|
||||
|
||||
public Fixes fixes;
|
||||
|
||||
public class Fixes extends ConfigurationPart {
|
||||
|
||||
public boolean broadcastCritAnimationsAsTheEntityBeingCritted = false; // Gale - MultiPaper - broadcast crit animations as the entity being critted
|
||||
|
||||
// Gale start - Purpur - fix MC-238526
|
||||
@Setting("mc-238526")
|
||||
public boolean mc238526 = false;
|
||||
// Gale end - Purpur - fix MC-238526
|
||||
|
||||
// Gale start - Purpur - fix MC-121706
|
||||
@Setting("mc-121706")
|
||||
public boolean mc121706 = false;
|
||||
// Gale end - Purpur - fix MC-121706
|
||||
|
||||
}
|
||||
|
||||
public boolean arrowMovementResetsDespawnCounter = true; // Gale - Purpur - make arrow movement resetting despawn counter configurable
|
||||
public boolean entitiesCanRandomStrollIntoNonTickingChunks = true; // Gale - MultiPaper - prevent entities random strolling into non-ticking chunks
|
||||
public double entityWakeUpDurationRatioStandardDeviation = 0.2; // Gale - variable entity wake-up duration
|
||||
public boolean hideFlamesOnEntitiesWithFireResistance = false; // Gale - Slice - hide flames on entities with fire resistance
|
||||
public boolean tryRespawnEnderDragonAfterEndCrystalPlace = true; // Gale - Pufferfish - make ender dragon respawn attempt after placing end crystals configurable
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
// Gale - branding changes - version fetcher
|
||||
|
||||
package org.galemc.gale.version;
|
||||
|
||||
import com.destroystokyo.paper.PaperVersionFetcher;
|
||||
import com.destroystokyo.paper.VersionHistoryManager;
|
||||
import com.destroystokyo.paper.util.VersionFetcher;
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import io.papermc.paper.ServerBuildInfo;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URI;
|
||||
import java.util.Optional;
|
||||
|
||||
import static net.kyori.adventure.text.Component.text;
|
||||
import static net.kyori.adventure.text.format.TextColor.color;
|
||||
|
||||
/**
|
||||
* An abstract version fetcher, derived from {@link PaperVersionFetcher}.
|
||||
* This class was then made to be a superclass of both {@link PaperVersionFetcher}
|
||||
* and {@link GaleVersionFetcher}.
|
||||
* <br>
|
||||
* Changes to {@link PaperVersionFetcher} are indicated by Gale marker comments.
|
||||
*/
|
||||
public abstract class AbstractPaperVersionFetcher implements VersionFetcher {
|
||||
protected static final Logger LOGGER = LogUtils.getClassLogger();
|
||||
protected static final int DISTANCE_ERROR = -1;
|
||||
protected static final int DISTANCE_UNKNOWN = -2;
|
||||
protected static final ServerBuildInfo BUILD_INFO = ServerBuildInfo.buildInfo();
|
||||
|
||||
// Gale start - branding changes - version fetcher
|
||||
protected final String gitHubBranchName;
|
||||
protected final String downloadPage;
|
||||
protected final String organizationDisplayName;
|
||||
protected final String projectDisplayName;
|
||||
protected final String gitHubOrganizationName;
|
||||
protected final String gitHubRepoName;
|
||||
|
||||
protected AbstractPaperVersionFetcher(String githubBranchName, String downloadPage, String organizationDisplayName, String projectDisplayName, String gitHubOrganizationName, String gitHubRepoName) {
|
||||
this.gitHubBranchName = githubBranchName;
|
||||
this.downloadPage = downloadPage;
|
||||
this.organizationDisplayName = organizationDisplayName;
|
||||
this.projectDisplayName = projectDisplayName;
|
||||
this.gitHubOrganizationName = gitHubOrganizationName;
|
||||
this.gitHubRepoName = gitHubRepoName;
|
||||
}
|
||||
// Gale end - branding changes - version fetcher
|
||||
|
||||
@Override
|
||||
public long getCacheTime() {
|
||||
return 720000;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Component getVersionMessage(final @NotNull String serverVersion) {
|
||||
final Component updateMessage;
|
||||
final ServerBuildInfo build = ServerBuildInfo.buildInfo();
|
||||
if (build.buildNumber().isEmpty() && build.gitCommit().isEmpty()) {
|
||||
updateMessage = text("You are running a development version without access to version information", color(0xFF5300));
|
||||
} else {
|
||||
updateMessage = getUpdateStatusMessage(this.gitHubOrganizationName + "/" + this.gitHubRepoName, build); // Gale - branding changes - version fetcher
|
||||
}
|
||||
final @Nullable Component history = this.getHistory();
|
||||
|
||||
return history != null ? Component.textOfChildren(updateMessage, Component.newline(), history) : updateMessage;
|
||||
}
|
||||
|
||||
// Gale start - branding changes - version fetcher
|
||||
protected boolean canFetchDistanceFromSiteApi() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected int fetchDistanceFromSiteApi(int jenkinsBuild) {
|
||||
return -1;
|
||||
}
|
||||
// Gale end - branding changes - version fetcher
|
||||
|
||||
private Component getUpdateStatusMessage(final String repo, final ServerBuildInfo build) {
|
||||
int distance = DISTANCE_ERROR;
|
||||
|
||||
// Gale start - branding changes - version fetcher
|
||||
final Optional<String> gitBranch = build.gitBranch();
|
||||
final Optional<String> gitCommit = build.gitCommit();
|
||||
if (gitBranch.isPresent() && gitCommit.isPresent()) {
|
||||
distance = fetchDistanceFromGitHub(repo, gitBranch.get(), gitCommit.get());
|
||||
}
|
||||
// Gale end - branding changes - version fetcher
|
||||
|
||||
return switch (distance) {
|
||||
case DISTANCE_ERROR -> text("Error obtaining version information", NamedTextColor.YELLOW);
|
||||
case 0 -> text("You are running the latest version", NamedTextColor.GREEN);
|
||||
case DISTANCE_UNKNOWN -> text("Unknown version", NamedTextColor.YELLOW);
|
||||
default -> text("You are " + distance + " version(s) behind", NamedTextColor.YELLOW)
|
||||
.append(Component.newline())
|
||||
.append(text("Download the new version at: ")
|
||||
.append(text(this.downloadPage, NamedTextColor.GOLD) // Gale - branding changes - version fetcher
|
||||
.hoverEvent(text("Click to open", NamedTextColor.WHITE))
|
||||
.clickEvent(ClickEvent.openUrl(this.downloadPage)))); // Gale - branding changes - version fetcher
|
||||
};
|
||||
}
|
||||
|
||||
// Contributed by Techcable <Techcable@outlook.com> in GH-65
|
||||
private static int fetchDistanceFromGitHub(final String repo, final String branch, final String hash) {
|
||||
try {
|
||||
final HttpURLConnection connection = (HttpURLConnection) URI.create("https://api.github.com/repos/%s/compare/%s...%s".formatted(repo, branch, hash)).toURL().openConnection();
|
||||
connection.connect();
|
||||
if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND)
|
||||
return DISTANCE_UNKNOWN; // Unknown commit
|
||||
try (final BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), Charsets.UTF_8))) {
|
||||
final JsonObject obj = new Gson().fromJson(reader, JsonObject.class);
|
||||
final String status = obj.get("status").getAsString();
|
||||
return switch (status) {
|
||||
case "identical" -> 0;
|
||||
case "behind" -> obj.get("behind_by").getAsInt();
|
||||
default -> DISTANCE_ERROR;
|
||||
};
|
||||
} catch (final JsonSyntaxException | NumberFormatException e) {
|
||||
LOGGER.error("Error parsing json from GitHub's API", e);
|
||||
return DISTANCE_ERROR;
|
||||
}
|
||||
} catch (final IOException e) {
|
||||
LOGGER.error("Error while parsing version", e);
|
||||
return DISTANCE_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Component getHistory() {
|
||||
final VersionHistoryManager.VersionData data = VersionHistoryManager.INSTANCE.getVersionData();
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final @Nullable String oldVersion = data.getOldVersion();
|
||||
if (oldVersion == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return text("Previous version: " + oldVersion, NamedTextColor.GRAY, TextDecoration.ITALIC);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Gale - semantic version
|
||||
|
||||
package org.galemc.gale.version;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A holder for the Gale semantic version.
|
||||
*/
|
||||
public final class GaleSemanticVersion {
|
||||
|
||||
private GaleSemanticVersion() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
/**
|
||||
* A semantic version in the format "<code>major.minor.patch</code>", for example "<code>1.5.1</code>".
|
||||
* The <code>major</code> version is incremented when a large and overarching set of features, with a large
|
||||
* and overarching common goal or effect, has been added compared to the first release with that major version.
|
||||
* The <code>minor</code> version is incremented for each build that has a different intended feature set
|
||||
* (for example, some features or part of them were added or removed).
|
||||
* The <code>patch</code> version is incremented for small changes that do not affect the goal of any feature,
|
||||
* such as bug fixes, performance improvements or changes in wording.
|
||||
*/
|
||||
public static final @NotNull String version = "0.6.15";
|
||||
|
||||
/**
|
||||
* The "<code>major.minor</code>" portion of the {@link #version}.
|
||||
*/
|
||||
public static final @NotNull String majorMinorVersion;
|
||||
|
||||
static {
|
||||
int firstDotIndex = version.indexOf('.');
|
||||
int secondDotIndex = version.indexOf('.', firstDotIndex + 1);
|
||||
majorMinorVersion = version.substring(0, secondDotIndex);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Gale - branding changes - version fetcher
|
||||
|
||||
package org.galemc.gale.version;
|
||||
|
||||
public class GaleVersionFetcher extends AbstractPaperVersionFetcher {
|
||||
|
||||
public GaleVersionFetcher() {
|
||||
super(
|
||||
"ver/1.21.4",
|
||||
"https://github.com/Dreeam-qwq/Gale",
|
||||
"GaleMC",
|
||||
"Gale",
|
||||
"GaleMC",
|
||||
"Gale");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Gale - virtual thread support
|
||||
|
||||
package org.galemc.gale.virtualthread;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
/**
|
||||
* An implementation of {@link VirtualThreadService} that can create virtual threads directly.
|
||||
*
|
||||
* @author Martijn Muijsers
|
||||
*/
|
||||
final class DirectVirtualThreadService extends VirtualThreadService {
|
||||
|
||||
private DirectVirtualThreadService() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ThreadFactory createFactory() {
|
||||
// Disabled until Minecraft requires servers to have a Java version that can read class files compiled with functionality from Java 19+ on preview / Java 21+ on stable
|
||||
//throw new UnsupportedOperationException();
|
||||
return Thread.ofVirtual().factory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Thread start(@NotNull Runnable task) {
|
||||
// Disabled until Minecraft requires servers to have a Java version that can read class files compiled with functionality from Java 19+ on preview / Java 21+ on stable
|
||||
//throw new UnsupportedOperationException();
|
||||
Objects.requireNonNull(task, "The task to start a virtual thread cannot be null");
|
||||
return Thread.ofVirtual().start(task);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A functional {@link DirectVirtualThreadService}.
|
||||
* @throws Throwable If creating virtual threads directly is not supported by the current runtime.
|
||||
* This could be any {@link Throwable}, including an {@link Exception} or an {@link Error}.
|
||||
*/
|
||||
static @NotNull DirectVirtualThreadService create() throws Throwable {
|
||||
// Disabled until Minecraft requires servers to have a Java version that can read class files compiled with functionality from Java 19+ on preview / Java 21+ on stable
|
||||
//throw new UnsupportedOperationException();
|
||||
var service = new DirectVirtualThreadService();
|
||||
// Run some tests to verify
|
||||
service.runTest();
|
||||
// If we end up here, it works
|
||||
return service;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// Gale - virtual thread support
|
||||
|
||||
package org.galemc.gale.virtualthread;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
/**
|
||||
* An implementation of {@link VirtualThreadService} that can create virtual threads using Java reflection.
|
||||
*
|
||||
* @author Martijn Muijsers
|
||||
*/
|
||||
final class ReflectionVirtualThreadService extends VirtualThreadService {
|
||||
|
||||
/**
|
||||
* The {@link Thread}<code>#ofVirtual()</code> method.
|
||||
*/
|
||||
private final @NotNull Method Thread_ofVirtual_method;
|
||||
|
||||
/**
|
||||
* The {@link Thread}<code>.Builder#factory()</code> method.
|
||||
*/
|
||||
private final @NotNull Method Thread_Builder_factory_method;
|
||||
|
||||
/**
|
||||
* The {@link Thread}<code>.Builder#start(Runnable)</code> method.
|
||||
*/
|
||||
private final @NotNull Method Thread_Builder_start_method;
|
||||
|
||||
private ReflectionVirtualThreadService() throws Throwable {
|
||||
this.Thread_ofVirtual_method = Objects.requireNonNull(Thread.class.getMethod("ofVirtual"));
|
||||
// The Thread.Builder class
|
||||
var Thread_Builder_class = Objects.requireNonNull(Class.forName("java.lang.Thread$Builder"));
|
||||
this.Thread_Builder_factory_method = Objects.requireNonNull(Thread_Builder_class.getMethod("factory"));
|
||||
this.Thread_Builder_start_method = Objects.requireNonNull(Thread_Builder_class.getMethod("start", Runnable.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ThreadFactory createFactory() {
|
||||
try {
|
||||
return (ThreadFactory) this.Thread_Builder_factory_method.invoke(this.Thread_ofVirtual_method.invoke(null));
|
||||
} catch (Exception e) {
|
||||
// This should not be possible because it was tested in create()
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Thread start(@NotNull Runnable task) {
|
||||
Objects.requireNonNull(task, "The task to start a virtual thread cannot be null");
|
||||
try {
|
||||
return (Thread) this.Thread_Builder_start_method.invoke(this.Thread_ofVirtual_method.invoke(null), task);
|
||||
} catch (Exception e) {
|
||||
// This should not be possible because it was tested in create()
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A functional {@link ReflectionVirtualThreadService}.
|
||||
* @throws Throwable If creating virtual threads via reflection is not supported by the current runtime.
|
||||
* This could be any {@link Throwable}, including an {@link Exception} or an {@link Error}.
|
||||
*/
|
||||
static @NotNull ReflectionVirtualThreadService create() throws Throwable {
|
||||
// This will already throw something if the reflection fails
|
||||
var service = new ReflectionVirtualThreadService();
|
||||
// Run some tests to verify
|
||||
service.runTest();
|
||||
// If we end up here, it works
|
||||
return service;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
// Gale - virtual thread support
|
||||
|
||||
package org.galemc.gale.virtualthread;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
/**
|
||||
* An abstract service to create virtual threads.
|
||||
*
|
||||
* @author Martijn Muijsers
|
||||
*/
|
||||
public sealed abstract class VirtualThreadService permits ReflectionVirtualThreadService, DirectVirtualThreadService {
|
||||
|
||||
/**
|
||||
* @return A {@link ThreadFactory} that produces virtual threads.
|
||||
*/
|
||||
public abstract @NotNull ThreadFactory createFactory();
|
||||
|
||||
/**
|
||||
* @param task The runnable for the thread to execute.
|
||||
* @return A virtual thread that has been started with the given task.
|
||||
*/
|
||||
public abstract @NotNull Thread start(Runnable task);
|
||||
|
||||
/**
|
||||
* Runs a test on the {@link #createFactory} and {@link #start} methods,
|
||||
* which certainly throws some {@link Throwable} if something goes wrong.
|
||||
*/
|
||||
protected void runTest() throws Throwable {
|
||||
// This will definitely throw something if it doesn't work
|
||||
try {
|
||||
this.start(() -> {}).join();
|
||||
} catch (InterruptedException ignored) {} // Except InterruptedException, we don't care about that one
|
||||
try {
|
||||
var thread = this.createFactory().newThread(() -> {});
|
||||
thread.start();
|
||||
thread.join();
|
||||
} catch (InterruptedException ignored) {} // Except InterruptedException, we don't care about that one
|
||||
// If we end up here, it works
|
||||
}
|
||||
|
||||
private static boolean initialized = false;
|
||||
|
||||
/**
|
||||
* The {@link VirtualThreadService} for the current runtime,
|
||||
* or null if virtual threads are not supported, or if not {@link #initialized} yet.
|
||||
*/
|
||||
private static @Nullable VirtualThreadService implementation;
|
||||
|
||||
/**
|
||||
* @return Whether virtual threads are supported on the current runtime.
|
||||
*/
|
||||
public static boolean isSupported() {
|
||||
return get() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The {@link VirtualThreadService} for the current runtime,
|
||||
* or null if virtual threads are not {@linkplain #isSupported() supported}.
|
||||
* <p>
|
||||
* This method is thread-safe only after the first time it has been fully run.
|
||||
*/
|
||||
public static @Nullable VirtualThreadService get() {
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
try {
|
||||
implementation = DirectVirtualThreadService.create();
|
||||
} catch (Throwable ignored) {
|
||||
try {
|
||||
implementation = ReflectionVirtualThreadService.create();
|
||||
} catch (Throwable ignored2) {}
|
||||
}
|
||||
}
|
||||
return implementation;
|
||||
}
|
||||
|
||||
/**
|
||||
* The minimum major version of Java that is known to support using virtual threads
|
||||
* (although possibly behind a feature preview flag).
|
||||
*/
|
||||
public static final int minimumJavaMajorVersionWithFeaturePreview = 19;
|
||||
|
||||
/**
|
||||
* The minimum major version of Java that is known to support using virtual threads
|
||||
* even without any feature preview flags.
|
||||
*/
|
||||
public static final int minimumJavaMajorVersionWithoutFeaturePreview = 21;
|
||||
|
||||
public static int getJavaMajorVersion() {
|
||||
var version = System.getProperty("java.version");
|
||||
if (version.startsWith("1.")) {
|
||||
return version.charAt(2) - '0';
|
||||
}
|
||||
if (version.contains("-")) {
|
||||
version = version.substring(0, version.indexOf("-"));
|
||||
}
|
||||
|
||||
int dotIndex = version.indexOf(".");
|
||||
return Integer.parseInt(dotIndex == -1 ? version : version.substring(0, dotIndex));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user