9
0
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:
Dreeam
2025-03-31 15:56:52 -04:00
parent e437419b6b
commit bbfb2815e4
83 changed files with 5 additions and 21 deletions

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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;
}
}

View File

@@ -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];
}

View File

@@ -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);
}
}

View File

@@ -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");
}
}

View File

@@ -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));
}
}
}
}
}

View File

@@ -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);
}
}

View 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() {
}
}

View File

@@ -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;
}
}

View File

@@ -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));
}
}

View File

@@ -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();
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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");
}
}

View File

@@ -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() {
}
}

View File

@@ -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;
}
}

View 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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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 {
}

View File

@@ -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 {
}

View File

@@ -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 {
}

View File

@@ -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 让玩家进入已满的服务器."""));
}
}

View File

@@ -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 配置里"""));
}
}

View File

@@ -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 漏洞!!"""));
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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.",
"使玩家可以击退僵尸."
));
}
}

View File

@@ -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",
"是否只允许玩家被实体推动"
));
}
}

View File

@@ -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.""",
"""
**实验性功能**
是否在玩家切换世界时尝试使用 "平滑传送".
此项要求源世界和目标世界逻辑高度相同才会生效."""
));
}
}

View File

@@ -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.",
"刷怪笼生成怪物之间的最大延迟 (以刻为单位). 较高的值会减缓刷怪笼的速度."
));
}
}

View File

@@ -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);
}
}

View File

@@ -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.",
"缓存过期时间. 单位: 分钟."
));
}
}

View File

@@ -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>");
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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.",
"移除修改无法编辑的告示牌时输出的警告."
));
}
}

View File

@@ -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 模式即可让玩家加入后端服务器."""));
}
}

View File

@@ -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.""",
"""
移除原版的用户名验证,
让所有字符均可作为玩家名."""));
}
}

View File

@@ -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);
}
}

View File

@@ -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));
}
}
}

View File

@@ -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);
}
}

View File

@@ -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>, 显示未知命令详细信息."""));
}
}

View File

@@ -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.
""",
"""
是否启用聊天签名, 禁用后玩家无法进行聊天举报."""));
}
}

View File

@@ -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 方法来优化非刷新数据包的发送,
避免在调度数据包操作时进行昂贵的线程唤醒调用.
需要重启服务器才能生效.
"""));
}
}

View File

@@ -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 列表缓存的间隔."));
}
}

View File

@@ -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);
}
}

View File

@@ -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")
);
}
}
}

View File

@@ -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的实体类型转换."""));
}
}

View File

@@ -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.");
}
}
}

View File

@@ -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.",
"更快的结构生成任务分段."));
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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."""));
}
}

View File

@@ -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 目标选择器.
有助于提升性能, 但对游戏有轻微影响."""));
}
}

View File

@@ -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);
}
}

View File

@@ -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.",
"是否为异步任务调度器使用虚拟线程."));
}
}

View File

@@ -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.",
"是否为异步聊天线程使用虚拟线程."));
}
}

View File

@@ -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.",
"是否为用户验证器使用虚拟线程."));
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View 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;
}
}

View 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;
}
}

View 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));
}
};
}
}

View File

@@ -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));
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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"
);
}
}

View File

@@ -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;
}
}

View File

@@ -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
}
}

View File

@@ -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 = {};
}

View File

@@ -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
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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");
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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));
}
}