entry : section.map.entrySet()) {
+ Node key = representer.represent(entry.getKey());
+
+ NodeCommentData childCommentData = null;
+ if (commentData != null) {
+ childCommentData = commentData.getChild(entry.getKey());
+ }
+
+ Node value;
+ if (entry.getValue() instanceof ConfigurationSection configurationSection) {
+ value = toNodeTree(childCommentData, configurationSection);
+ } else {
+ value = representer.represent(entry.getValue());
+ }
+
+ NodeTuple nodeTuple = new NodeTuple(key, value);
+ if (childCommentData != null) {
+ childCommentData.apply(nodeTuple);
+ }
+
+ nodeTuples.add(nodeTuple);
+ }
+
+ return new MappingNode(Tag.MAP, nodeTuples, DumperOptions.FlowStyle.BLOCK);
+ }
+
+ private void fromNodeTree(@NotNull MappingNode input, @NotNull NodeCommentData commentData,
+ @NotNull ConfigurationSection section) throws InvalidConfigurationException {
+ constructor.flattenMapping(input);
+
+ for (NodeTuple nodeTuple : input.getValue()) {
+ Node keyNode = nodeTuple.getKeyNode();
+ String key = String.valueOf(constructor.constructObject(keyNode));
+ if (key.isBlank() || key.indexOf(ConfigurationSection.PATH_SEPARATOR) != -1) {
+ throw new InvalidConfigurationException(String.format("Invalid key%s%n key can't be blank or contain any '%s'",
+ keyNode.getStartMark(), ConfigurationSection.PATH_SEPARATOR));
+ }
+
+ Node valueNode = nodeTuple.getValueNode();
+ while (valueNode instanceof AnchorNode value) {
+ valueNode = value.getRealNode();
+ }
+
+ NodeCommentData childCommentData = new NodeCommentData(nodeTuple);
+ commentData.addChild(key, childCommentData);
+
+ if (valueNode instanceof MappingNode value) {
+ fromNodeTree(value, childCommentData, section.createSection(key));
+ } else {
+ section.set(key, constructor.constructObject(valueNode));
+ }
+ }
+ }
+
+ private boolean isNullOrEmpty(Collection> collection) {
+ return collection == null || collection.isEmpty();
+ }
+}
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/yaml/YamlConstructor.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/yaml/YamlConstructor.java
new file mode 100644
index 000000000..9acf5a40c
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/yaml/YamlConstructor.java
@@ -0,0 +1,23 @@
+package dev.imprex.orebfuscator.config.yaml;
+
+import org.yaml.snakeyaml.LoaderOptions;
+import org.yaml.snakeyaml.constructor.Constructor;
+import org.yaml.snakeyaml.nodes.MappingNode;
+import org.yaml.snakeyaml.nodes.Node;
+
+class YamlConstructor extends Constructor {
+
+ public YamlConstructor(LoaderOptions loadingConfig) {
+ super(loadingConfig);
+ }
+
+ @Override
+ public void flattenMapping(MappingNode node) {
+ super.flattenMapping(node);
+ }
+
+ @Override
+ public Object constructObject(Node node) {
+ return super.constructObject(node);
+ }
+}
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/yaml/package-info.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/yaml/package-info.java
new file mode 100644
index 000000000..380b89609
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/config/yaml/package-info.java
@@ -0,0 +1,15 @@
+/**
+ * This package contains classes adapted or derived from the Bukkit Project, specifically the configuration package
+ * available at:
+ * https://hub.spigotmc.org/stash/projects/SPIGOT/repos/bukkit/browse/src/main/java/org/bukkit/configuration
+ *
+ * Original authors and contributors of the Bukkit Project hold copyright over the original work. The code is licensed
+ * under the GNU General Public License v3.0 (GPLv3), and this project continues to adhere to that license.
+ *
+ * Copyright (C) 2011-2024 Bukkit Project (original authors and contributors) License: GNU General Public License v3.0
+ * (GPLv3)
+ *
+ * For more information, visit: https://hub.spigotmc.org/stash/projects/SPIGOT/repos/bukkit
+ */
+
+package dev.imprex.orebfuscator.config.yaml;
\ No newline at end of file
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/ChunkPacketAccessor.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/ChunkPacketAccessor.java
new file mode 100644
index 000000000..e77b8533d
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/ChunkPacketAccessor.java
@@ -0,0 +1,21 @@
+package dev.imprex.orebfuscator.interop;
+
+import java.util.function.Predicate;
+import dev.imprex.orebfuscator.util.BlockPos;
+
+public interface ChunkPacketAccessor {
+
+ WorldAccessor world();
+
+ int chunkX();
+
+ int chunkZ();
+
+ boolean isSectionPresent(int index);
+
+ byte[] data();
+
+ void setData(byte[] data);
+
+ void filterBlockEntities(Predicate predicate);
+}
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/RegistryAccessor.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/RegistryAccessor.java
new file mode 100644
index 000000000..3f316c2b7
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/RegistryAccessor.java
@@ -0,0 +1,24 @@
+package dev.imprex.orebfuscator.interop;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import dev.imprex.orebfuscator.util.BlockProperties;
+import dev.imprex.orebfuscator.util.BlockTag;
+
+public interface RegistryAccessor {
+
+ int getUniqueBlockStateCount();
+
+ int getMaxBitsPerBlockState();
+
+ boolean isAir(int blockId);
+
+ boolean isOccluding(int blockId);
+
+ boolean isBlockEntity(int blockId);
+
+ @Nullable BlockProperties getBlockByName(@NotNull String name);
+
+ @Nullable BlockTag getBlockTagByName(@NotNull String name);
+
+}
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/ServerAccessor.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/ServerAccessor.java
new file mode 100644
index 000000000..c10e72b44
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/ServerAccessor.java
@@ -0,0 +1,21 @@
+package dev.imprex.orebfuscator.interop;
+
+import java.nio.file.Path;
+import java.util.List;
+import dev.imprex.orebfuscator.util.Version;
+
+public interface ServerAccessor {
+
+ Path getConfigDirectory();
+
+ Path getWorldDirectory();
+
+ String getOrebfuscatorVersion();
+
+ Version getMinecraftVersion();
+
+ RegistryAccessor getRegistry();
+
+ List getWorlds();
+
+}
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/WorldAccessor.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/WorldAccessor.java
new file mode 100644
index 000000000..9661af574
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/interop/WorldAccessor.java
@@ -0,0 +1,20 @@
+package dev.imprex.orebfuscator.interop;
+
+public interface WorldAccessor {
+
+ String getName();
+
+ int getHeight();
+
+ int getMinBuildHeight();
+
+ int getMaxBuildHeight();
+
+ int getSectionCount();
+
+ int getMinSection();
+
+ int getMaxSection();
+
+ int getSectionIndex(int y);
+}
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/logging/LogLevel.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/logging/LogLevel.java
new file mode 100644
index 000000000..fc538f743
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/logging/LogLevel.java
@@ -0,0 +1,5 @@
+package dev.imprex.orebfuscator.logging;
+
+public enum LogLevel {
+ DEBUG, INFO, WARN, ERROR
+}
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/logging/LoggerAccessor.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/logging/LoggerAccessor.java
new file mode 100644
index 000000000..22bade655
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/logging/LoggerAccessor.java
@@ -0,0 +1,10 @@
+package dev.imprex.orebfuscator.logging;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public interface LoggerAccessor {
+
+ void log(@NotNull LogLevel level, @NotNull String message, @Nullable Throwable throwable);
+
+}
\ No newline at end of file
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/logging/OfcLogger.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/logging/OfcLogger.java
new file mode 100644
index 000000000..00b16f334
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/logging/OfcLogger.java
@@ -0,0 +1,81 @@
+package dev.imprex.orebfuscator.logging;
+
+import java.util.Objects;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class OfcLogger {
+
+ private static LoggerAccessor logger = new SystemLogger();
+
+ private static final Queue VERBOSE_LOG = new ConcurrentLinkedQueue<>();
+ private static boolean verbose;
+
+ public static void setLogger(@NotNull LoggerAccessor logger) {
+ if (OfcLogger.logger instanceof SystemLogger) {
+ OfcLogger.logger = Objects.requireNonNull(logger);
+ }
+ }
+
+ public static void setVerboseLogging(boolean enabled) {
+ if (!verbose && enabled) {
+ verbose = true;
+ debug("Verbose logging has been enabled");
+ } else {
+ verbose = enabled;
+ }
+ }
+
+ @NotNull
+ public static String getLatestVerboseLog() {
+ return String.join("\n", VERBOSE_LOG);
+ }
+
+ public static void debug(@NotNull String message) {
+ log(LogLevel.DEBUG, message);
+ }
+
+ public static void info(@NotNull String message) {
+ log(LogLevel.INFO, message);
+ }
+
+ public static void warn(@NotNull String message) {
+ log(LogLevel.WARN, message);
+ }
+
+ public static void error(@NotNull Throwable throwable) {
+ log(LogLevel.ERROR, "An error occurred:", throwable);
+ }
+
+ public static void error(@NotNull String message, @Nullable Throwable throwable) {
+ log(LogLevel.ERROR, message, throwable);
+ }
+
+ public static void log(@NotNull LogLevel level, @NotNull String message) {
+ log(level, message, null);
+ }
+
+ public static void log(@NotNull LogLevel level, @NotNull String message, @Nullable Throwable throwable) {
+ Objects.requireNonNull(level);
+ Objects.requireNonNull(message);
+
+ if (level == LogLevel.DEBUG) {
+ // always store debug messages for system dumps
+ while (VERBOSE_LOG.size() >= 1000) {
+ VERBOSE_LOG.poll();
+ }
+
+ VERBOSE_LOG.offer(message);
+
+ // filter out debug if verbose logging is disabled
+ if (!verbose) {
+ return;
+ }
+ }
+
+ logger.log(level, message, throwable);
+ }
+}
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/logging/SystemLogger.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/logging/SystemLogger.java
new file mode 100644
index 000000000..921a2f07e
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/logging/SystemLogger.java
@@ -0,0 +1,19 @@
+package dev.imprex.orebfuscator.logging;
+
+import java.io.PrintStream;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class SystemLogger implements LoggerAccessor {
+
+ @Override
+ public void log(@NotNull LogLevel level, @NotNull String message, @Nullable Throwable throwable) {
+ PrintStream stream = level == LogLevel.ERROR ? System.err : System.out;
+ stream.printf("[Orebfuscator - %s] %s%n", level, message);
+
+ if (throwable != null) {
+ throwable.printStackTrace(stream);
+ }
+ }
+}
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/Reflector.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/Reflector.java
new file mode 100644
index 000000000..6e823248d
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/Reflector.java
@@ -0,0 +1,103 @@
+package dev.imprex.orebfuscator.reflect;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+import org.jetbrains.annotations.NotNull;
+import dev.imprex.orebfuscator.reflect.accessor.Accessors;
+import dev.imprex.orebfuscator.reflect.accessor.ConstructorAccessor;
+import dev.imprex.orebfuscator.reflect.accessor.FieldAccessor;
+import dev.imprex.orebfuscator.reflect.accessor.MethodAccessor;
+import dev.imprex.orebfuscator.reflect.predicate.ConstructorPredicate;
+import dev.imprex.orebfuscator.reflect.predicate.FieldPredicate;
+import dev.imprex.orebfuscator.reflect.predicate.MethodPredicate;
+
+public class Reflector {
+
+ public static Reflector of(Class> target) {
+ return new Reflector(target);
+ }
+
+ private final @NotNull Class> target;
+ private @NotNull Class> recursiveUntil;
+
+ private Reflector(@NotNull Class> target) {
+ this.target = Objects.requireNonNull(target);
+ this.recursiveUntil = target;
+ }
+
+ public Reflector recursive() {
+ return this.recursiveUntil(Object.class);
+ }
+
+ public Reflector recursiveUntil(Class> recursiveUntil) {
+ this.recursiveUntil = recursiveUntil != null
+ ? recursiveUntil
+ : this.target;
+ return this;
+ }
+
+ private String className() {
+ if (target == recursiveUntil) {
+ return target.getTypeName();
+ } else if (recursiveUntil == Object.class) {
+ return String.format("%s recursively", target.getTypeName());
+ } else {
+ return String.format("%s recursively until %s", this.target.getTypeName(),
+ this.recursiveUntil.getTypeName());
+ }
+ }
+
+ private Stream get(Function, T[]> getter) {
+ Class> current = this.target;
+ Stream stream = Stream.empty();
+
+ while (current != null) {
+ stream = Stream.concat(stream, Arrays.stream(getter.apply(current)));
+ if (current == recursiveUntil) {
+ break;
+ }
+ current = current.getSuperclass();
+ }
+
+ return stream;
+ }
+
+ @NotNull
+ public Stream constructor(Predicate> predicate) {
+ Stream> stream = get(Class::getDeclaredConstructors);
+ return stream.filter(predicate).map(Accessors::wrap);
+ }
+
+ @NotNull
+ public ConstructorPredicate constructor() {
+ return new ConstructorPredicate(this::constructor, this::className);
+ }
+
+ @NotNull
+ public Stream field(Predicate predicate) {
+ Stream stream = get(Class::getDeclaredFields);
+ return stream.filter(predicate).map(Accessors::wrap);
+ }
+
+ @NotNull
+ public FieldPredicate field() {
+ return new FieldPredicate(this::field, this::className);
+ }
+
+ @NotNull
+ public Stream method(Predicate predicate) {
+ Stream stream = get(Class::getDeclaredMethods);
+ return stream.filter(predicate).map(Accessors::wrap);
+ }
+
+ @NotNull
+ public MethodPredicate method() {
+ return new MethodPredicate(this::method, this::className);
+ }
+}
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/Accessors.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/Accessors.java
new file mode 100644
index 000000000..2fc4b4502
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/Accessors.java
@@ -0,0 +1,109 @@
+package dev.imprex.orebfuscator.reflect.accessor;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodHandles.Lookup;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Objects;
+import org.jetbrains.annotations.NotNull;
+
+public final class Accessors {
+
+ private static final Lookup LOOKUP = MethodHandles.lookup();
+
+ private static final MethodType FIELD_GETTER = MethodType.methodType(Object.class, Object.class);
+ private static final MethodType FIELD_SETTER = MethodType.methodType(void.class, Object.class, Object.class);
+
+ private Accessors() {
+ }
+
+ public static @NotNull ConstructorAccessor wrap(@NotNull Constructor> constructor) {
+ return create(constructor, () -> {
+ MethodHandle methodHandle = LOOKUP.unreflectConstructor(constructor);
+ methodHandle = generifyExecutable(methodHandle, false, true);
+
+ return new DefaultConstructorAccessor(constructor, methodHandle);
+ });
+ }
+
+ public static @NotNull FieldAccessor wrap(@NotNull Field field) {
+ return create(field, () -> {
+ MethodHandle getter = LOOKUP.unreflectGetter(field);
+ MethodHandle setter = null;
+
+ try {
+ setter = LOOKUP.unreflectSetter(field);
+ } catch (IllegalAccessException e) {
+ }
+
+ if (Modifier.isStatic(field.getModifiers())) {
+ getter = MethodHandles.dropArguments(getter, 0, Object.class);
+ setter = setter != null ? MethodHandles.dropArguments(setter, 0, Object.class) : null;
+ }
+
+ getter = getter.asType(FIELD_GETTER);
+ setter = setter != null ? setter.asType(FIELD_SETTER) : null;
+
+ return new DefaultFieldAccessor(field, getter, setter);
+ });
+ }
+
+ public static @NotNull MethodAccessor wrap(@NotNull Method method) {
+ return create(method, () -> {
+ MethodHandle methodHandle = LOOKUP.unreflect(method);
+ methodHandle = generifyExecutable(methodHandle, Modifier.isStatic(method.getModifiers()), false);
+
+ return new DefaultMethodAccessor(method, methodHandle);
+ });
+ }
+
+ private static @NotNull TAccessor create(
+ @NotNull TMember member, @NotNull AccessorFactory factory) {
+ Objects.requireNonNull(member);
+
+ @SuppressWarnings("deprecation")
+ boolean accessible = member.isAccessible();
+ try {
+ member.setAccessible(true);
+
+ return factory.create();
+ } catch (IllegalAccessException e) {
+ throw new IllegalStateException("Unable to create accessor for " + member, e);
+ } finally {
+ member.setAccessible(accessible);
+ }
+ }
+
+ private static MethodHandle generifyExecutable(MethodHandle handle, boolean isStatic, boolean isConstructor) {
+ // force the method to use a fixed arity, ensuring it accepts varargs directly as an array
+ MethodHandle target = handle.asFixedArity();
+
+ // determine the number of parameters to spread: subtract 1 for instance methods, as the first parameter is the
+ // receiver (the instance object)
+ int paramCount = handle.type().parameterCount() - (isConstructor || isStatic ? 0 : 1);
+
+ // spread the Object[] arguments into individual parameters (after the instance object for non-static methods)
+ target = target.asSpreader(Object[].class, paramCount);
+
+ if (isStatic) {
+ // add a dummy instance parameter at the beginning for static methods to unify the calling convention with
+ // instance methods
+ target = MethodHandles.dropArguments(target, 0, Object.class);
+ }
+
+ // convert the MethodHandle to a generic signature:
+ // return Object, take Object instance (receiver) if needed, and an Object[] for the remaining arguments
+ return target.asType(MethodType.genericMethodType(isConstructor ? 0 : 1, true));
+ }
+
+ private interface AccessorFactory {
+
+ T create() throws IllegalAccessException;
+ }
+}
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/ConstructorAccessor.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/ConstructorAccessor.java
new file mode 100644
index 000000000..132538feb
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/ConstructorAccessor.java
@@ -0,0 +1,9 @@
+package dev.imprex.orebfuscator.reflect.accessor;
+
+import java.lang.reflect.Constructor;
+
+public interface ConstructorAccessor extends MemberAccessor> {
+
+ Object invoke(Object... args);
+
+}
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/DefaultConstrutorAccessor.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/DefaultConstrutorAccessor.java
new file mode 100644
index 000000000..58b41fb7f
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/DefaultConstrutorAccessor.java
@@ -0,0 +1,24 @@
+package dev.imprex.orebfuscator.reflect.accessor;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.reflect.Constructor;
+
+record DefaultConstructorAccessor(@NotNull Constructor> member, @NotNull MethodHandle methodHandle) implements
+ ConstructorAccessor {
+
+ @Override
+ public Object invoke(Object... args) {
+ try {
+ return methodHandle.invokeExact(args);
+ } catch (Throwable throwable) {
+ throw new IllegalStateException("Unable to construct new instance using " + member, throwable);
+ }
+ }
+
+ @Override
+ public @NotNull Constructor> member() {
+ return member;
+ }
+}
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/DefaultFieldAccessor.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/DefaultFieldAccessor.java
new file mode 100644
index 000000000..c91dab56e
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/DefaultFieldAccessor.java
@@ -0,0 +1,42 @@
+package dev.imprex.orebfuscator.reflect.accessor;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.reflect.Field;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+record DefaultFieldAccessor(@NotNull Field member, @NotNull MethodHandle getterHandle,
+ @Nullable MethodHandle setterHandle) implements FieldAccessor {
+
+ @Override
+ public boolean readonly() {
+ return setterHandle == null;
+ }
+
+ @Override
+ public Object get(Object instance) {
+ try {
+ return getterHandle.invokeExact(instance);
+ } catch (Throwable throwable) {
+ throw new IllegalStateException("Unable to get field value of " + member, throwable);
+ }
+ }
+
+ @Override
+ public void set(Object instance, Object value) {
+ if (readonly()) {
+ throw new IllegalStateException("Can't set value of trusted final field " + member);
+ }
+
+ try {
+ setterHandle.invokeExact(instance, value);
+ } catch (Throwable throwable) {
+ throw new IllegalStateException("Unable to set value of field " + member, throwable);
+ }
+ }
+
+ @Override
+ public @NotNull Field member() {
+ return this.member;
+ }
+}
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/DefaultMethodAccessor.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/DefaultMethodAccessor.java
new file mode 100644
index 000000000..940bb2f59
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/DefaultMethodAccessor.java
@@ -0,0 +1,23 @@
+package dev.imprex.orebfuscator.reflect.accessor;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.reflect.Method;
+import org.jetbrains.annotations.NotNull;
+
+record DefaultMethodAccessor(@NotNull Method member, @NotNull MethodHandle methodHandle) implements
+ MethodAccessor {
+
+ @Override
+ public Object invoke(Object instance, Object... args) {
+ try {
+ return methodHandle.invokeExact(instance, args);
+ } catch (Throwable throwable) {
+ throw new IllegalStateException("Unable to invoke method " + member, throwable);
+ }
+ }
+
+ @Override
+ public @NotNull Method member() {
+ return member;
+ }
+}
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/FieldAccessor.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/FieldAccessor.java
new file mode 100644
index 000000000..d69b72ff9
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/FieldAccessor.java
@@ -0,0 +1,13 @@
+package dev.imprex.orebfuscator.reflect.accessor;
+
+import java.lang.reflect.Field;
+
+public interface FieldAccessor extends MemberAccessor {
+
+ boolean readonly();
+
+ Object get(Object instance);
+
+ void set(Object instance, Object value);
+
+}
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/MemberAccessor.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/MemberAccessor.java
new file mode 100644
index 000000000..ce43b857f
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/MemberAccessor.java
@@ -0,0 +1,10 @@
+package dev.imprex.orebfuscator.reflect.accessor;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.lang.reflect.Member;
+
+public interface MemberAccessor {
+
+ @NotNull TMember member();
+}
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/MethodAccessor.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/MethodAccessor.java
new file mode 100644
index 000000000..11198eb80
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/accessor/MethodAccessor.java
@@ -0,0 +1,9 @@
+package dev.imprex.orebfuscator.reflect.accessor;
+
+import java.lang.reflect.Method;
+
+public interface MethodAccessor extends MemberAccessor {
+
+ Object invoke(Object target, Object... args);
+
+}
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/AbstractExecutablePredicate.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/AbstractExecutablePredicate.java
new file mode 100644
index 000000000..d3c6722a3
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/AbstractExecutablePredicate.java
@@ -0,0 +1,148 @@
+package dev.imprex.orebfuscator.reflect.predicate;
+
+import java.lang.reflect.Executable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import dev.imprex.orebfuscator.reflect.accessor.MemberAccessor;
+
+abstract sealed class AbstractExecutablePredicate<
+ TThis extends AbstractExecutablePredicate,
+ TAccessor extends MemberAccessor,
+ TExecutable extends Executable
+ > extends AbstractMemberPredicate permits ConstructorPredicate, MethodPredicate {
+
+ private final @NotNull List exceptionClass = new ArrayList<>();
+ private final @NotNull List parameterClass = new ArrayList<>();
+ private int parameterCount = -1;
+
+ public AbstractExecutablePredicate(
+ @NotNull Function> producer,
+ @NotNull Supplier error) {
+ super(producer, error);
+ }
+
+ @Override
+ public boolean test(@NotNull TExecutable executable) {
+ return super.test(executable)
+ && IndexedClassMatcher.all(executable.getExceptionTypes(), exceptionClass)
+ && IndexedClassMatcher.all(executable.getParameterTypes(), parameterClass)
+ && (parameterCount < 0 || parameterCount == executable.getParameterCount());
+ }
+
+ @Override
+ void requirements(@NotNull RequirementCollector collector) {
+ super.requirements(collector);
+
+ if (!exceptionClass.isEmpty()) {
+ collector.collect("exceptionClass", IndexedClassMatcher.toString(exceptionClass));
+ }
+ if (!parameterClass.isEmpty()) {
+ collector.collect("parameterClass", IndexedClassMatcher.toString(parameterClass));
+ }
+ if (parameterCount >= 0) {
+ collector.collect("parameterCount", parameterCount);
+ }
+ }
+
+ public @NotNull TThis exception(@NotNull ClassPredicate matcher) {
+ this.exceptionClass.add(new IndexedClassMatcher(Objects.requireNonNull(matcher)));
+ return instance();
+ }
+
+ public @NotNull TThis exception(@NotNull ClassPredicate matcher, int index) {
+ this.exceptionClass.add(new IndexedClassMatcher(Objects.requireNonNull(matcher), index));
+ return instance();
+ }
+
+ public @NotNull ClassPredicate.Builder exception() {
+ return new ClassPredicate.Builder<>(this::exception);
+ }
+
+ public @NotNull ClassPredicate.Builder exception(int index) {
+ return new ClassPredicate.Builder<>(m -> this.exception(m, index));
+ }
+
+ public @NotNull TThis parameter(@NotNull ClassPredicate matcher) {
+ this.parameterClass.add(new IndexedClassMatcher(Objects.requireNonNull(matcher)));
+ return instance();
+ }
+
+ public @NotNull TThis parameter(@NotNull ClassPredicate matcher, int index) {
+ this.parameterClass.add(new IndexedClassMatcher(Objects.requireNonNull(matcher), index));
+ return instance();
+ }
+
+ public @NotNull ClassPredicate.Builder parameter() {
+ return new ClassPredicate.Builder<>(this::parameter);
+ }
+
+ public @NotNull ClassPredicate.Builder parameter(int index) {
+ return new ClassPredicate.Builder<>(m -> this.parameter(m, index));
+ }
+
+ public @NotNull TThis parameterCount(int parameterCount) {
+ this.parameterCount = parameterCount;
+ return instance();
+ }
+
+ private record IndexedClassMatcher(@NotNull ClassPredicate matcher, @Nullable Integer index) implements
+ Comparable {
+
+ private static boolean all(@NotNull Class>[] classArray, @NotNull List classMatchers) {
+ return classMatchers.stream().allMatch(matcher -> matcher.matches(classArray));
+ }
+
+ private static String toString(@NotNull List classMatchers) {
+ return classMatchers.stream()
+ .sorted()
+ .map(IndexedClassMatcher::toString)
+ .collect(Collectors.joining(",\n ", "{\n ", "\n }"));
+ }
+
+ public IndexedClassMatcher(@NotNull ClassPredicate matcher) {
+ this(matcher, null);
+ }
+
+ public boolean matches(@NotNull Class>[] classArray) {
+ if (index() == null) {
+ for (Class> entry : classArray) {
+ if (matcher().test(entry)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ return index() < classArray.length && matcher().test(classArray[index()]);
+ }
+
+ @Override
+ public int compareTo(@NotNull IndexedClassMatcher other) {
+ if (this.index == null && other.index == null) {
+ return 0;
+ }
+ if (this.index == null) {
+ return -1;
+ }
+ if (other.index == null) {
+ return 1;
+ }
+ return this.index.compareTo(other.index);
+ }
+
+ @Override
+ public @NotNull String toString() {
+ String key = index() == null ? "" : index().toString();
+ return String.format("%s=%s", key, matcher().requirement());
+ }
+ }
+}
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/AbstractMemberPredicate.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/AbstractMemberPredicate.java
new file mode 100644
index 000000000..5cff71ac0
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/AbstractMemberPredicate.java
@@ -0,0 +1,192 @@
+package dev.imprex.orebfuscator.reflect.predicate;
+
+import java.lang.reflect.Member;
+import java.lang.reflect.Modifier;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+
+import org.intellij.lang.annotations.MagicConstant;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import dev.imprex.orebfuscator.reflect.accessor.MemberAccessor;
+
+abstract sealed class AbstractMemberPredicate<
+ TThis extends AbstractMemberPredicate,
+ TAccessor extends MemberAccessor,
+ TMember extends Member
+ > implements Predicate permits AbstractExecutablePredicate, FieldPredicate {
+
+ private final @NotNull Function> producer;
+ private final @NotNull Supplier error;
+
+ private int requiredModifiers;
+ private int bannedModifiers;
+ private boolean includeSynthetic;
+ private @Nullable Pattern name;
+ private @Nullable ClassPredicate declaringClass;
+
+ public AbstractMemberPredicate(
+ @NotNull Function> producer,
+ @NotNull Supplier error) {
+ this.producer = producer;
+ this.error = error;
+ }
+
+ @Override
+ public boolean test(@NotNull TMember member) {
+ int modifiers = member.getModifiers();
+ return (modifiers & requiredModifiers) == requiredModifiers
+ && (modifiers & bannedModifiers) == 0
+ && (includeSynthetic || !member.isSynthetic())
+ && (name == null || name.matcher(member.getName()).matches())
+ && (declaringClass == null || declaringClass.test(member.getDeclaringClass()));
+ }
+
+ void requirements(@NotNull RequirementCollector collector) {
+ if (requiredModifiers != 0) {
+ collector.collect("requiredModifiers", Modifier.toString(requiredModifiers));
+ }
+ if (bannedModifiers != 0) {
+ collector.collect("bannedModifiers", Modifier.toString(bannedModifiers));
+ }
+ if (includeSynthetic) {
+ collector.collect("includeSynthetic");
+ }
+ if (name != null) {
+ collector.collect("name", name);
+ }
+ if (declaringClass != null) {
+ collector.collect("declaringClass", declaringClass.requirement());
+ }
+ }
+
+ private @NotNull IllegalArgumentException requirementException() {
+ var collector = new RequirementCollector(error.get());
+ requirements(collector);
+ return new IllegalArgumentException(collector.get());
+ }
+
+ protected abstract @NotNull TThis instance();
+
+ public @NotNull TThis requireModifier(@MagicConstant(flagsFromClass = Modifier.class) int modifiers) {
+ this.requiredModifiers |= modifiers;
+ return instance();
+ }
+
+ public @NotNull TThis requirePublic() {
+ return requireModifier(Modifier.PUBLIC);
+ }
+
+ public @NotNull TThis requireProtected() {
+ return requireModifier(Modifier.PROTECTED);
+ }
+
+ public @NotNull TThis requirePrivate() {
+ return requireModifier(Modifier.PRIVATE);
+ }
+
+ public @NotNull TThis requireStatic() {
+ return requireModifier(Modifier.STATIC);
+ }
+
+ public @NotNull TThis requireFinal() {
+ return requireModifier(Modifier.FINAL);
+ }
+
+ public @NotNull TThis banModifier(@MagicConstant(flagsFromClass = Modifier.class) int modifiers) {
+ this.bannedModifiers |= modifiers;
+ return instance();
+ }
+
+ public @NotNull TThis banPublic() {
+ return banModifier(Modifier.PUBLIC);
+ }
+
+ public @NotNull TThis banProtected() {
+ return banModifier(Modifier.PROTECTED);
+ }
+
+ public @NotNull TThis banPrivate() {
+ return banModifier(Modifier.PRIVATE);
+ }
+
+ public @NotNull TThis banStatic() {
+ return banModifier(Modifier.STATIC);
+ }
+
+ public @NotNull TThis banFinal() {
+ return banModifier(Modifier.FINAL);
+ }
+
+ public @NotNull TThis includeSynthetic() {
+ this.includeSynthetic = true;
+ return instance();
+ }
+
+ public @NotNull TThis nameRegex(@NotNull Pattern pattern) {
+ this.name = Objects.requireNonNull(pattern);
+ return instance();
+ }
+
+ public @NotNull TThis nameIs(@NotNull String name) {
+ String pattern = Pattern.quote(Objects.requireNonNull(name));
+ return nameRegex(Pattern.compile(pattern));
+ }
+
+ public @NotNull TThis nameIsIgnoreCase(@NotNull String name) {
+ String pattern = Pattern.quote(Objects.requireNonNull(name));
+ return nameRegex(Pattern.compile(pattern, Pattern.CASE_INSENSITIVE));
+ }
+
+ public @NotNull TThis declaringClass(@NotNull ClassPredicate matcher) {
+ this.declaringClass = Objects.requireNonNull(matcher);
+ return instance();
+ }
+
+ public @NotNull ClassPredicate.Builder declaringClass() {
+ return new ClassPredicate.Builder<>(this::declaringClass);
+ }
+
+ @Contract(pure = true)
+ public @NotNull Stream stream() {
+ return producer.apply(instance());
+ }
+
+ @Contract(pure = true)
+ public @Nullable TAccessor get(int index) {
+ return stream().skip(index).findFirst().orElse(null);
+ }
+
+ @Contract(pure = true)
+ public @NotNull TAccessor getOrThrow(int index) {
+ return stream().skip(index).findFirst().orElseThrow(this::requirementException);
+ }
+
+ @Contract(pure = true)
+ public @Nullable TAccessor first() {
+ return stream().findFirst().orElse(null);
+ }
+
+ @Contract(pure = true)
+ public @NotNull TAccessor firstOrThrow() {
+ return stream().findFirst().orElseThrow(this::requirementException);
+ }
+
+ @Contract(pure = true)
+ public @Nullable TAccessor find(@NotNull Predicate predicate) {
+ return stream().filter(accessor -> predicate.test(accessor.member()))
+ .findFirst().orElse(null);
+ }
+
+ @Contract(pure = true)
+ public @NotNull TAccessor findOrThrow(@NotNull Predicate predicate) {
+ return stream().filter(accessor -> predicate.test(accessor.member()))
+ .findFirst().orElseThrow(this::requirementException);
+ }
+}
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/ClassPredicate.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/ClassPredicate.java
new file mode 100644
index 000000000..09e223674
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/ClassPredicate.java
@@ -0,0 +1,192 @@
+package dev.imprex.orebfuscator.reflect.predicate;
+
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import org.jetbrains.annotations.NotNull;
+
+public interface ClassPredicate extends Predicate> {
+
+ boolean test(@NotNull Class> type);
+
+ @NotNull
+ String requirement();
+
+ class Builder {
+
+ private final Function returnFunction;
+
+ public Builder(Function returnFunction) {
+ this.returnFunction = returnFunction;
+ }
+
+ @NotNull
+ public TParent is(@NotNull Class> type) {
+ return returnFunction.apply(new IsClassPredicate(type));
+ }
+
+ @NotNull
+ public TParent superOf(@NotNull Class> type) {
+ return returnFunction.apply(new SuperClassPredicate(type));
+ }
+
+ @NotNull
+ public TParent subOf(@NotNull Class> type) {
+ return returnFunction.apply(new SubClassPredicate(type));
+ }
+
+ @NotNull
+ public TParent any(@NotNull Set> types) {
+ return returnFunction.apply(new AnyClassPredicate(types));
+ }
+
+ @NotNull
+ public TParent any(@NotNull Class>... types) {
+ return any(Set.of(types));
+ }
+
+ @NotNull
+ public TParent regex(@NotNull Pattern pattern) {
+ return returnFunction.apply(new RegexClassPredicate(pattern));
+ }
+ }
+
+ class IsClassPredicate implements ClassPredicate {
+
+ private final @NotNull Class> expected;
+
+ public IsClassPredicate(@NotNull Class> expected) {
+ this.expected = Objects.requireNonNull(expected);
+ }
+
+ @Override
+ public boolean test(@NotNull Class> type) {
+ Objects.requireNonNull(type);
+
+ return this.expected.equals(type);
+ }
+
+ @NotNull
+ @Override
+ public String requirement() {
+ return String.format("{is %s}", this.expected.getTypeName());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return this == obj || (obj instanceof IsClassPredicate other && Objects.equals(this.expected, other.expected));
+ }
+ }
+
+ class SuperClassPredicate implements ClassPredicate {
+
+ private final @NotNull Class> expected;
+
+ public SuperClassPredicate(@NotNull Class> expected) {
+ this.expected = Objects.requireNonNull(expected);
+ }
+
+ @Override
+ public boolean test(@NotNull Class> type) {
+ Objects.requireNonNull(type);
+
+ return type.isAssignableFrom(this.expected);
+ }
+
+ @NotNull
+ @Override
+ public String requirement() {
+ return String.format("{super-class-of %s}", this.expected.getTypeName());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return this == obj || (obj instanceof SuperClassPredicate other && Objects.equals(this.expected, other.expected));
+ }
+ }
+
+ class SubClassPredicate implements ClassPredicate {
+
+ private final @NotNull Class> expected;
+
+ public SubClassPredicate(@NotNull Class> expected) {
+ this.expected = Objects.requireNonNull(expected);
+ }
+
+ @Override
+ public boolean test(@NotNull Class> type) {
+ Objects.requireNonNull(type);
+
+ return this.expected.isAssignableFrom(type);
+ }
+
+ @NotNull
+ @Override
+ public String requirement() {
+ return String.format("{sub-class-of %s}", this.expected.getTypeName());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return this == obj || (obj instanceof SubClassPredicate other && Objects.equals(this.expected, other.expected));
+ }
+ }
+
+ class AnyClassPredicate implements ClassPredicate {
+
+ private final @NotNull Set> expected;
+
+ public AnyClassPredicate(@NotNull Set> expected) {
+ this.expected = Objects.requireNonNull(expected);
+ }
+
+ @Override
+ public boolean test(@NotNull Class> type) {
+ Objects.requireNonNull(type);
+
+ return this.expected.contains(type);
+ }
+
+ @NotNull
+ @Override
+ public String requirement() {
+ return String.format("{any %s}",
+ this.expected.stream().map(Class::getTypeName).collect(Collectors.joining(", ", "(", ")")));
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return this == obj || (obj instanceof AnyClassPredicate other && Objects.equals(this.expected, other.expected));
+ }
+ }
+
+ class RegexClassPredicate implements ClassPredicate {
+
+ private final @NotNull Pattern expected;
+
+ public RegexClassPredicate(@NotNull Pattern expected) {
+ this.expected = Objects.requireNonNull(expected);
+ }
+
+ @Override
+ public boolean test(@NotNull Class> type) {
+ Objects.requireNonNull(type);
+
+ return this.expected.matcher(type.getTypeName()).matches();
+ }
+
+ @NotNull
+ @Override
+ public String requirement() {
+ return String.format("{regex %s}", this.expected);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return this == obj || (obj instanceof RegexClassPredicate other && Objects.equals(this.expected, other.expected));
+ }
+ }
+}
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/ConstructorPredicate.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/ConstructorPredicate.java
new file mode 100644
index 000000000..d1e89d3a3
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/ConstructorPredicate.java
@@ -0,0 +1,25 @@
+package dev.imprex.orebfuscator.reflect.predicate;
+
+import java.lang.reflect.Constructor;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+
+import org.jetbrains.annotations.NotNull;
+
+import dev.imprex.orebfuscator.reflect.accessor.ConstructorAccessor;
+
+public final class ConstructorPredicate
+ extends AbstractExecutablePredicate> {
+
+ public ConstructorPredicate(
+ @NotNull Function> producer,
+ @NotNull Supplier className) {
+ super(producer, () -> String.format("Can't find constructor in class %s matching: ", className.get()));
+ }
+
+ @Override
+ protected @NotNull ConstructorPredicate instance() {
+ return this;
+ }
+}
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/FieldPredicate.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/FieldPredicate.java
new file mode 100644
index 000000000..6af1db372
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/FieldPredicate.java
@@ -0,0 +1,50 @@
+package dev.imprex.orebfuscator.reflect.predicate;
+
+import dev.imprex.orebfuscator.reflect.accessor.FieldAccessor;
+import java.lang.reflect.Field;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public final class FieldPredicate extends AbstractMemberPredicate {
+
+ private @Nullable ClassPredicate type;
+
+ public FieldPredicate(
+ @NotNull Function> producer,
+ @NotNull Supplier className) {
+ super(producer, () -> String.format("Can't find field in class %s matching: ", className.get()));
+ }
+
+ @Override
+ public boolean test(@NotNull Field field) {
+ return super.test(field)
+ && (type == null || type.test(field.getType()));
+ }
+
+ @Override
+ void requirements(@NotNull RequirementCollector collector) {
+ super.requirements(collector);
+
+ if (type != null) {
+ collector.collect("type", type.requirement());
+ }
+ }
+
+ public @NotNull FieldPredicate type(@NotNull ClassPredicate matcher) {
+ this.type = Objects.requireNonNull(matcher);
+ return this;
+ }
+
+ public @NotNull ClassPredicate.Builder type() {
+ return new ClassPredicate.Builder<>(this::type);
+ }
+
+ @Override
+ protected @NotNull FieldPredicate instance() {
+ return this;
+ }
+}
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/MethodPredicate.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/MethodPredicate.java
new file mode 100644
index 000000000..462d6e83b
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/MethodPredicate.java
@@ -0,0 +1,50 @@
+package dev.imprex.orebfuscator.reflect.predicate;
+
+import dev.imprex.orebfuscator.reflect.accessor.MethodAccessor;
+import java.lang.reflect.Method;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public final class MethodPredicate extends AbstractExecutablePredicate {
+
+ private @Nullable ClassPredicate returnType;
+
+ public MethodPredicate(
+ @NotNull Function> producer,
+ @NotNull Supplier className) {
+ super(producer, () -> String.format("Can't find constructor in class %s matching: ", className.get()));
+ }
+
+ @Override
+ public boolean test(@NotNull Method method) {
+ return super.test(method)
+ && (returnType == null || returnType.test(method.getReturnType()));
+ }
+
+ @Override
+ void requirements(@NotNull RequirementCollector collector) {
+ super.requirements(collector);
+
+ if (returnType != null) {
+ collector.collect("returnType", returnType.requirement());
+ }
+ }
+
+ public @NotNull MethodPredicate returnType(@NotNull ClassPredicate matcher) {
+ this.returnType = Objects.requireNonNull(matcher);
+ return this;
+ }
+
+ public @NotNull ClassPredicate.Builder returnType() {
+ return new ClassPredicate.Builder<>(this::returnType);
+ }
+
+ @Override
+ protected @NotNull MethodPredicate instance() {
+ return this;
+ }
+}
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/RequirementCollector.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/RequirementCollector.java
new file mode 100644
index 000000000..d1a91e771
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/reflect/predicate/RequirementCollector.java
@@ -0,0 +1,36 @@
+package dev.imprex.orebfuscator.reflect.predicate;
+
+import java.util.Objects;
+import java.util.StringJoiner;
+import org.jetbrains.annotations.NotNull;
+
+class RequirementCollector {
+
+ private final StringJoiner entries;
+
+ public RequirementCollector(@NotNull String prefix) {
+ this.entries = new StringJoiner(",\n", prefix + "{\n", "\n}");
+ }
+
+ @NotNull
+ public RequirementCollector collect(@NotNull String name) {
+ Objects.requireNonNull(name);
+
+ entries.add(" " + name);
+ return this;
+ }
+
+ @NotNull
+ public RequirementCollector collect(@NotNull String name, @NotNull Object value) {
+ Objects.requireNonNull(name);
+ Objects.requireNonNull(value);
+
+ entries.add(" " + name + ": " + value);
+ return this;
+ }
+
+ @NotNull
+ public String get() {
+ return entries.toString();
+ }
+}
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/BlockPos.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/BlockPos.java
new file mode 100644
index 000000000..8e46b483f
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/BlockPos.java
@@ -0,0 +1,71 @@
+package dev.imprex.orebfuscator.util;
+
+public record BlockPos(int x, int y, int z) implements Comparable {
+
+ // from net.minecraft.core.BlockPos
+ private static final int BITS_PER_X = 26;
+ private static final int BITS_PER_Z = BITS_PER_X;
+ private static final int BITS_PER_Y = 64 - BITS_PER_X - BITS_PER_Z;
+
+ private static final int OFFSET_Y = 0;
+ private static final int OFFSET_Z = OFFSET_Y + BITS_PER_Y;
+ private static final int OFFSET_X = OFFSET_Z + BITS_PER_Z;
+
+ private static final long MASK_X = (1L << BITS_PER_X) - 1L;
+ private static final long MASK_Y = (1L << BITS_PER_Y) - 1L;
+ private static final long MASK_Z = (1L << BITS_PER_Z) - 1L;
+
+ // from net.minecraft.world.level.dimension.DimensionType
+ public static final int Y_SIZE = (1 << BITS_PER_Y) - 32;
+ public static final int MAX_Y = (Y_SIZE >> 1) - 1;
+ public static final int MIN_Y = MAX_Y - Y_SIZE + 1;
+
+ public BlockPos add(int x, int y, int z) {
+ return x == 0 && y == 0 && z == 0 ? this : new BlockPos(this.x + x, this.y + y, this.z + z);
+ }
+
+ public double distanceSquared(double x, double y, double z) {
+ double dx = this.x - x;
+ double dy = this.y - y;
+ double dz = this.z - z;
+ return dx * dx + dy * dy + dz * dz;
+ }
+
+ public long toLong() {
+ return (this.x & MASK_X) << OFFSET_X | (this.y & MASK_Y) << OFFSET_Y | (this.z & MASK_Z) << OFFSET_Z;
+ }
+
+ public static BlockPos fromLong(long value) {
+ int x = (int) (value << (64 - BITS_PER_X - OFFSET_X) >> (64 - BITS_PER_X));
+ int y = (int) (value << (64 - BITS_PER_Y - OFFSET_Y) >> (64 - BITS_PER_Y));
+ int z = (int) (value << (64 - BITS_PER_Z - OFFSET_Z) >> (64 - BITS_PER_Z));
+ return new BlockPos(x, y, z);
+ }
+
+ public int toSectionPos() {
+ return (this.x & 0xF) << 12 | (this.y & 0xFFF) << 0 | (this.z & 0xF) << 16;
+ }
+
+ public static BlockPos fromSectionPos(int x, int z, int sectionPos) {
+ x += (sectionPos >> 12) & 0xF;
+ int y = (sectionPos << 20 >> 20);
+ z += (sectionPos >> 16) & 0xF;
+ return new BlockPos(x, y, z);
+ }
+
+ @Override
+ public int compareTo(BlockPos other) {
+ if (this.y == other.y) {
+ if (this.z == other.z) {
+ return this.x - other.x;
+ }
+ return this.z - other.z;
+ }
+ return this.y - other.y;
+ }
+
+ @Override
+ public String toString() {
+ return "BlockPos [x=" + x + ", y=" + y + ", z=" + z + "]";
+ }
+}
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/BlockProperties.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/BlockProperties.java
new file mode 100644
index 000000000..30a0561ce
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/BlockProperties.java
@@ -0,0 +1,98 @@
+package dev.imprex.orebfuscator.util;
+
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+import com.google.common.collect.ImmutableList;
+
+public class BlockProperties {
+
+ public static Builder builder(NamespacedKey key) {
+ return new Builder(key);
+ }
+
+ private final NamespacedKey key;
+ private final BlockStateProperties defaultBlockState;
+ private final ImmutableList blockStates;
+
+ private BlockProperties(Builder builder) {
+ this.key = builder.key;
+ this.defaultBlockState = builder.defaultBlockState;
+ this.blockStates = ImmutableList.copyOf(builder.blockStates);
+ }
+
+ public NamespacedKey getKey() {
+ return key;
+ }
+
+ public BlockStateProperties getDefaultBlockState() {
+ return defaultBlockState;
+ }
+
+ public ImmutableList getBlockStates() {
+ return blockStates;
+ }
+
+ @Override
+ public int hashCode() {
+ return this.key.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof BlockProperties other)) {
+ return false;
+ }
+ return Objects.equals(key, other.key);
+ }
+
+ @Override
+ public String toString() {
+ return "BlockProperties [key=" + key + ", defaultBlockState=" + defaultBlockState + ", blockStates=" + blockStates
+ + "]";
+ }
+
+ public static class Builder {
+
+ private final NamespacedKey key;
+
+ private BlockStateProperties defaultBlockState;
+ private final Set blockStates = new HashSet<>();
+
+ private Builder(NamespacedKey key) {
+ this.key = key;
+ }
+
+ public Builder withBlockState(BlockStateProperties blockState) {
+ if (!blockStates.add(blockState)) {
+ throw new IllegalStateException(
+ String.format("duplicate block state id (%s) for block: %s", blockState.getId(), key));
+ }
+
+ if (blockState.isDefaultState()) {
+ // check for multiple default blocks
+ if (this.defaultBlockState != null) {
+ throw new IllegalStateException(
+ String.format("multiple default block states for block: %s", blockState.getId(), key));
+ }
+
+ this.defaultBlockState = blockState;
+ }
+
+ return this;
+ }
+
+ public BlockProperties build() {
+ Objects.requireNonNull(this.defaultBlockState, "missing default block state for block: " + this.key);
+
+ if (this.blockStates.size() == 0) {
+ throw new IllegalStateException("missing block states for block: " + this.key);
+ }
+
+ return new BlockProperties(this);
+ }
+ }
+}
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/BlockStateProperties.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/BlockStateProperties.java
new file mode 100644
index 000000000..9c53c5f33
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/BlockStateProperties.java
@@ -0,0 +1,103 @@
+package dev.imprex.orebfuscator.util;
+
+public class BlockStateProperties {
+
+ public static Builder builder(int id) {
+ return new Builder(id);
+ }
+
+ private final int id;
+
+ private final boolean isAir;
+ private final boolean isOccluding;
+ private final boolean isBlockEntity;
+ private final boolean isDefaultState;
+
+ private BlockStateProperties(Builder builder) {
+ this.id = builder.id;
+ this.isAir = builder.isAir;
+ this.isOccluding = builder.isOccluding;
+ this.isBlockEntity = builder.isBlockEntity;
+ this.isDefaultState = builder.isDefaultState;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public boolean isAir() {
+ return isAir;
+ }
+
+ public boolean isOccluding() {
+ return isOccluding;
+ }
+
+ public boolean isBlockEntity() {
+ return isBlockEntity;
+ }
+
+ public boolean isDefaultState() {
+ return isDefaultState;
+ }
+
+ @Override
+ public int hashCode() {
+ return id;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof BlockStateProperties other)) {
+ return false;
+ }
+ return id == other.id;
+ }
+
+ @Override
+ public String toString() {
+ return "BlockStateProperties [id=" + id + ", isDefaultState=" + isDefaultState + ", isAir=" + isAir
+ + ", isOccluding=" + isOccluding + ", isBlockEntity=" + isBlockEntity + "]";
+ }
+
+ public static class Builder {
+
+ private final int id;
+
+ private boolean isAir;
+ private boolean isOccluding;
+ private boolean isBlockEntity;
+ private boolean isDefaultState;
+
+ private Builder(int id) {
+ this.id = id;
+ }
+
+ public Builder withIsAir(boolean isAir) {
+ this.isAir = isAir;
+ return this;
+ }
+
+ public Builder withIsOccluding(boolean isOccluding) {
+ this.isOccluding = isOccluding;
+ return this;
+ }
+
+ public Builder withIsBlockEntity(boolean isBlockEntity) {
+ this.isBlockEntity = isBlockEntity;
+ return this;
+ }
+
+ public Builder withIsDefaultState(boolean isDefaultState) {
+ this.isDefaultState = isDefaultState;
+ return this;
+ }
+
+ public BlockStateProperties build() {
+ return new BlockStateProperties(this);
+ }
+ }
+}
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/BlockTag.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/BlockTag.java
new file mode 100644
index 000000000..5769bda45
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/BlockTag.java
@@ -0,0 +1,30 @@
+package dev.imprex.orebfuscator.util;
+
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Set;
+import org.jetbrains.annotations.NotNull;
+
+public record BlockTag(@NotNull NamespacedKey key, @NotNull Set blocks) {
+
+ public BlockTag(@NotNull NamespacedKey key, @NotNull Set blocks) {
+ this.key = key;
+ this.blocks = Collections.unmodifiableSet(blocks);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.key.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof BlockTag other)) {
+ return false;
+ }
+ return Objects.equals(key, other.key);
+ }
+}
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/ChunkCacheKey.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/ChunkCacheKey.java
new file mode 100644
index 000000000..2de791560
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/ChunkCacheKey.java
@@ -0,0 +1,15 @@
+package dev.imprex.orebfuscator.util;
+
+import org.jetbrains.annotations.NotNull;
+import dev.imprex.orebfuscator.interop.WorldAccessor;
+
+public record ChunkCacheKey(@NotNull String world, int x, int z) {
+
+ public ChunkCacheKey(@NotNull WorldAccessor world, BlockPos position) {
+ this(world.getName(), position.x() >> 4, position.z() >> 4);
+ }
+
+ public ChunkCacheKey(@NotNull WorldAccessor world, int x, int z) {
+ this(world.getName(), x, z);
+ }
+}
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/ChunkDirection.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/ChunkDirection.java
new file mode 100644
index 000000000..a9f65cb33
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/ChunkDirection.java
@@ -0,0 +1,40 @@
+package dev.imprex.orebfuscator.util;
+
+public enum ChunkDirection {
+
+ NORTH(1, 0), EAST(0, 1), SOUTH(-1, 0), WEST(0, -1);
+
+ private final int offsetX;
+ private final int offsetZ;
+
+ ChunkDirection(int offsetX, int offsetZ) {
+ this.offsetX = offsetX;
+ this.offsetZ = offsetZ;
+ }
+
+ public int getOffsetX() {
+ return offsetX;
+ }
+
+ public int getOffsetZ() {
+ return offsetZ;
+ }
+
+ public static ChunkDirection fromPosition(ChunkCacheKey key, int targetX, int targetZ) {
+ int offsetX = (targetX >> 4) - key.x();
+ int offsetZ = (targetZ >> 4) - key.z();
+
+ if (offsetX == 1 && offsetZ == 0) {
+ return NORTH;
+ } else if (offsetX == 0 && offsetZ == 1) {
+ return EAST;
+ } else if (offsetX == -1 && offsetZ == 0) {
+ return SOUTH;
+ } else if (offsetX == 0 && offsetZ == -1) {
+ return WEST;
+ }
+
+ throw new IllegalArgumentException(
+ String.format("invalid offset (origin: %s, x: %d, z: %d)", key, targetX, targetZ));
+ }
+}
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/JavaVersion.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/JavaVersion.java
new file mode 100644
index 000000000..fdf6e155b
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/JavaVersion.java
@@ -0,0 +1,36 @@
+package dev.imprex.orebfuscator.util;
+
+public final class JavaVersion {
+
+ private static final int JAVA_VERSION = javaMajorVersion();
+
+ public static int get() {
+ return JAVA_VERSION;
+ }
+
+ private static int javaMajorVersion() {
+ return majorVersion(System.getProperty("java.specification.version", "1.6"));
+ }
+
+ /**
+ * taken from:
+ * https://github.com/netty/netty/blob/7ad2b91515b3affaeadb4b2975cd6d2a8342c403/common/src/main/java/io/netty/util/internal/PlatformDependent0.java#L1037
+ */
+ private static int majorVersion(final String javaSpecVersion) {
+ final String[] components = javaSpecVersion.split("\\.");
+ final int[] version = new int[components.length];
+ for (int i = 0; i < components.length; i++) {
+ version[i] = Integer.parseInt(components[i]);
+ }
+
+ if (version[0] == 1) {
+ assert version[1] >= 6;
+ return version[1];
+ } else {
+ return version[0];
+ }
+ }
+
+ private JavaVersion() {
+ }
+}
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/MathUtil.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/MathUtil.java
new file mode 100644
index 000000000..095a8fa87
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/MathUtil.java
@@ -0,0 +1,25 @@
+package dev.imprex.orebfuscator.util;
+
+public class MathUtil {
+
+ public static int ceilToPowerOfTwo(int value) {
+ value--;
+ value |= value >> 1;
+ value |= value >> 2;
+ value |= value >> 4;
+ value |= value >> 8;
+ value |= value >> 16;
+ value++;
+ return value;
+ }
+
+ public static int clamp(int value, int min, int max) {
+ return Math.max(min, Math.min(max, value));
+ }
+
+ public static int ceilLog2(int value) {
+ int result = 31 - Integer.numberOfLeadingZeros(value);
+ // add 1 if value is NOT a power of 2 (to do the ceil)
+ return result + (value != 0 && (value & value - 1) == 0 ? 0 : 1);
+ }
+}
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/NamespacedKey.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/NamespacedKey.java
new file mode 100644
index 000000000..3a029d3b1
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/NamespacedKey.java
@@ -0,0 +1,152 @@
+package dev.imprex.orebfuscator.util;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Represents a String based key which consists of two components - a namespace and a key.
+ *
+ * Namespaces may only contain lowercase alphanumeric characters, periods, underscores, and hyphens.
+ *
+ * Keys may only contain lowercase alphanumeric characters, periods, underscores, hyphens, and forward slashes.
+ *
+ * @author org.bukkit.NamespacedKey from 1.19.4
+ *
+ */
+public record NamespacedKey(String namespace, String key) {
+
+ /**
+ * The namespace representing all inbuilt keys.
+ */
+ public static final String MINECRAFT = "minecraft";
+
+ private static boolean isValidNamespaceChar(char c) {
+ return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '.' || c == '_' || c == '-';
+ }
+
+ private static boolean isValidKeyChar(char c) {
+ return isValidNamespaceChar(c) || c == '/';
+ }
+
+ private static boolean isValidNamespace(String namespace) {
+ int len = namespace.length();
+ if (len == 0) {
+ return false;
+ }
+
+ for (int i = 0; i < len; i++) {
+ if (!isValidNamespaceChar(namespace.charAt(i))) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static boolean isValidKey(String key) {
+ int len = key.length();
+ if (len == 0) {
+ return false;
+ }
+
+ for (int i = 0; i < len; i++) {
+ if (!isValidKeyChar(key.charAt(i))) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Create a key in a specific namespace.
+ *
+ * @param namespace namespace
+ * @param key key
+ * @deprecated should never be used by plugins, for internal use only!!
+ */
+ @Deprecated
+ public NamespacedKey(String namespace, String key) {
+ if (namespace == null || !isValidNamespace(namespace)) {
+ throw new IllegalArgumentException(String.format("Invalid namespace. Must be [a-z0-9._-]: %s", namespace));
+ } else if (key == null || !isValidKey(key)) {
+ throw new IllegalArgumentException(String.format("Invalid key. Must be [a-z0-9/._-]: %s", key));
+ }
+
+ this.namespace = namespace;
+ this.key = key;
+
+ String string = toString();
+ if (string.length() >= 256) {
+ throw new IllegalArgumentException(String.format("NamespacedKey must be less than 256 characters (%s)", string));
+ }
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final NamespacedKey other = (NamespacedKey) obj;
+ return this.namespace.equals(other.namespace) && this.key.equals(other.key);
+ }
+
+ @Override
+ public String toString() {
+ return this.namespace + ":" + this.key;
+ }
+
+ /**
+ * Get a key in the Minecraft namespace.
+ *
+ * @param key the key to use
+ * @return new key in the Minecraft namespace
+ */
+ public static NamespacedKey minecraft(String key) {
+ return new NamespacedKey(MINECRAFT, key);
+ }
+
+
+ /**
+ * Get a NamespacedKey from the supplied string.
+ *
+ * The default namespace will be Minecraft's (i.e. {@link #minecraft(String)}).
+ *
+ * @return the created NamespacedKey. null if invalid
+ */
+ public static NamespacedKey fromString(@NotNull String string) {
+ if (string == null || string.isEmpty()) {
+ throw new IllegalArgumentException("Input string must not be empty or null");
+ }
+
+ String[] components = string.split(":", 3);
+ if (components.length > 2) {
+ return null;
+ }
+
+ String key = (components.length == 2) ? components[1] : "";
+ if (components.length == 1) {
+ String value = components[0];
+ if (value.isEmpty() || !isValidKey(value)) {
+ return null;
+ }
+
+ return minecraft(value);
+ } else if (components.length == 2 && !isValidKey(key)) {
+ return null;
+ }
+
+ String namespace = components[0];
+ if (namespace.isEmpty()) {
+ return minecraft(key);
+ }
+
+ if (!isValidNamespace(namespace)) {
+ return null;
+ }
+
+ return new NamespacedKey(namespace, key);
+ }
+}
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/SimpleCache.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/SimpleCache.java
new file mode 100644
index 000000000..d51221387
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/SimpleCache.java
@@ -0,0 +1,32 @@
+package dev.imprex.orebfuscator.util;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+
+/**
+ * Simple cache implementation that removes the oldest element once a certain size is reached
+ */
+public class SimpleCache extends LinkedHashMap {
+
+ private static final long serialVersionUID = -2732738355560313649L;
+
+ private final int maximumSize;
+ private final Consumer> remove;
+
+ public SimpleCache(int maximumSize, Consumer> remove) {
+ super(16, 0.75f, true);
+
+ this.maximumSize = maximumSize;
+ this.remove = remove;
+ }
+
+ @Override
+ protected boolean removeEldestEntry(Map.Entry entry) {
+ if (this.size() > this.maximumSize) {
+ this.remove.accept(entry);
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/Version.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/Version.java
new file mode 100644
index 000000000..6a0b4b886
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/Version.java
@@ -0,0 +1,120 @@
+package dev.imprex.orebfuscator.util;
+
+import java.io.IOException;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+
+public record Version(int major, int minor, int patch) implements Comparable {
+
+ private static final Pattern VERSION_PATTERN =
+ Pattern.compile("(?\\d+)(?:\\.(?\\d+))?(?:\\.(?\\d+))?");
+
+ public static Version parse(String version) {
+ return tryParse(version)
+ .orElseThrow(() -> new IllegalArgumentException("Can't parse version: " + version));
+ }
+
+ public static Optional tryParse(String version) {
+ Matcher matcher = VERSION_PATTERN.matcher(version);
+
+ if (!matcher.find()) {
+ return Optional.empty();
+ }
+
+ int major = Integer.parseInt(matcher.group("major"));
+
+ String minorGroup = matcher.group("minor");
+ int minor = minorGroup != null ? Integer.parseInt(minorGroup) : 0;
+
+ String patchGroup = matcher.group("patch");
+ int patch = patchGroup != null ? Integer.parseInt(patchGroup) : 0;
+
+ return Optional.of(new Version(major, minor, patch));
+ }
+
+ public boolean isAbove(String version) {
+ return this.isAbove(Version.parse(version));
+ }
+
+ public boolean isAbove(Version version) {
+ return this.compareTo(version) > 0;
+ }
+
+ public boolean isAtOrAbove(String version) {
+ return this.isAtOrAbove(Version.parse(version));
+ }
+
+ public boolean isAtOrAbove(Version version) {
+ return this.compareTo(version) >= 0;
+ }
+
+ public boolean isAtOrBelow(String version) {
+ return this.isAtOrBelow(Version.parse(version));
+ }
+
+ public boolean isAtOrBelow(Version version) {
+ return this.compareTo(version) <= 0;
+ }
+
+ public boolean isBelow(String version) {
+ return this.isBelow(Version.parse(version));
+ }
+
+ public boolean isBelow(Version version) {
+ return this.compareTo(version) < 0;
+ }
+
+ @Override
+ public int compareTo(Version other) {
+ int major = Integer.compare(this.major, other.major);
+ if (major != 0) {
+ return major;
+ }
+
+ int minor = Integer.compare(this.minor, other.minor);
+ if (minor != 0) {
+ return minor;
+ }
+
+ return Integer.compare(this.patch, other.patch);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(major, minor, patch);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof Version other)) {
+ return false;
+ }
+ return major == other.major && minor == other.minor && patch == other.patch;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s.%s.%s", this.major, this.minor, this.patch);
+ }
+
+ public static final class Json extends TypeAdapter {
+
+ @Override
+ public void write(JsonWriter out, Version value) throws IOException {
+ out.value(value.toString());
+ }
+
+ @Override
+ public Version read(JsonReader in) throws IOException {
+ return Version.parse(in.nextString());
+ }
+ }
+}
diff --git a/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/WeightedRandom.java b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/WeightedRandom.java
new file mode 100644
index 000000000..ac40fd346
--- /dev/null
+++ b/orebfuscator-core/src/main/java/dev/imprex/orebfuscator/util/WeightedRandom.java
@@ -0,0 +1,208 @@
+package dev.imprex.orebfuscator.util;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.random.RandomGenerator;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Weighted random integer sampler using the
+ * alias method.
+ *
+ * This data structure allows efficient sampling of integer values according to arbitrary (positive) weights:
+ *
+ * - Preprocessing/build time: {@code O(n)}
+ * - Sampling time: {@code O(1)}
+ * - Memory usage: {@code O(n)}
+ *
+ *
+ * Usage example:
+ *
{@code
+ * WeightedRandom wr = WeightedRandom.builder()
+ * .add(1, 0.5) // value 1 with weight 0.5
+ * .add(2, 1.5) // value 2 with weight 1.5
+ * .add(3, 3.0) // value 3 with weight 3.0
+ * .build();
+ *
+ * int sample = wr.next(); // sample ~10% chance of 1, 30% of 2, 60% of 3
+ * }
+ *
+ *
+ * This implementation is immutable and thread-safe for concurrent calls to
+ * {@link #next()} after construction.
+ */
+public final class WeightedRandom {
+
+ /**
+ * Creates a new builder for constructing a {@link WeightedRandom}.
+ *
+ * @return a new {@link Builder} instance
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ private final int n;
+ private final double totalWeight;
+ private final boolean allWeightsEqual;
+
+ private final int[] values;
+ private final double[] probabilities;
+ private final int[] alias;
+
+ /**
+ * Constructs a weighted random sampler from the given builder. Implements the alias method preprocessing algorithm.
+ *
+ * @param builder the builder containing values and weights
+ */
+ private WeightedRandom(@NotNull Builder builder) {
+ double minWeight = Double.POSITIVE_INFINITY;
+ double maxWeight = Double.NEGATIVE_INFINITY;
+ double totalWeight = 0d;
+
+ // implementation from: https://www.keithschwarz.com/darts-dice-coins/
+ // STEP 1
+ this.n = builder.entries.size();
+ this.values = new int[n];
+ this.probabilities = new double[n];
+ this.alias = new int[n];
+
+ // STEP 2
+ double[] scaled = new double[n];
+ Deque small = new ArrayDeque<>();
+ Deque large = new ArrayDeque<>();
+
+ // STEP 3
+ int index = 0;
+ for (var entry : builder.entries.entrySet()) {
+ values[index] = entry.getKey();
+
+ double weight = entry.getValue();
+ minWeight = Math.min(minWeight, weight);
+ maxWeight = Math.max(maxWeight, weight);
+ totalWeight += weight;
+
+ scaled[index++] = weight * n;
+ }
+
+ this.totalWeight = totalWeight;
+
+ // treat near-equal weights as equal
+ double span = maxWeight - minWeight;
+ if (span <= Math.ulp(maxWeight) * 4 || span <= 1e-12 * Math.max(1.0, maxWeight)) {
+ this.allWeightsEqual = true;
+ return;
+ }
+ this.allWeightsEqual = false;
+
+ // STEP 4
+ for (int i = 0; i < n; i++) {
+ if (scaled[i] < totalWeight) {
+ small.addLast(i);
+ } else {
+ large.addLast(i);
+ }
+ }
+
+ // STEP 5
+ while (!small.isEmpty() && !large.isEmpty()) {
+ int l = small.removeLast();
+ int g = large.removeLast();
+ probabilities[l] = scaled[l];
+ alias[l] = g;
+ scaled[g] = scaled[g] + scaled[l] - totalWeight;
+ if (scaled[g] < totalWeight) {
+ small.addLast(g);
+ } else {
+ large.addLast(g);
+ }
+ }
+
+ // STEP 6
+ while (!large.isEmpty()) {
+ probabilities[large.removeLast()] = totalWeight;
+ }
+
+ // STEP 7
+ while (!small.isEmpty()) {
+ probabilities[small.removeLast()] = totalWeight;
+ }
+ }
+
+ /**
+ * Samples a random value using {@link ThreadLocalRandom}.
+ *
+ * @return a sampled integer according to the configured weights
+ */
+ public int next() {
+ return next(ThreadLocalRandom.current());
+ }
+
+ /**
+ * Samples a random value using the given random generator.
+ *
+ * @param random a {@link RandomGenerator} to use for randomness
+ * @return a sampled integer according to the configured weights
+ */
+ public int next(@NotNull RandomGenerator random) {
+ Objects.requireNonNull(random);
+
+ int i = random.nextInt(this.n);
+ if (this.allWeightsEqual) {
+ return values[i];
+ }
+
+ int pick = random.nextDouble(totalWeight) < probabilities[i] ? i : alias[i];
+ return values[pick];
+ }
+
+ /**
+ * Builder for {@link WeightedRandom}.
+ *
+ * Values are unique; adding the same value multiple times will merge and sum their weights.
+ */
+ public static class Builder {
+
+ private final Map entries = new LinkedHashMap<>();
+
+ private Builder() {
+ }
+
+ /**
+ * Adds a value with the given weight to the distribution.
+ *
+ * If the value already exists, its weight is increased by the given amount.
+ *
+ * @param value the integer value to add
+ * @param weight the weight associated with this value (must be positive and finite)
+ * @return this builder for chaining
+ * @throws IllegalArgumentException if {@code weight <= 0} or not finite
+ */
+ public Builder add(int value, double weight) {
+ if (weight <= 0.0d || !Double.isFinite(weight)) {
+ throw new IllegalArgumentException("Weight has to be greater zero and finite!");
+ }
+
+ this.entries.merge(value, weight, (a, b) -> a + b);
+ return this;
+ }
+
+ /**
+ * Builds a {@link WeightedRandom} from the current set of values and weights.
+ *
+ * @return a new {@link WeightedRandom} instance
+ * @throws IllegalStateException if no values have been added
+ */
+ public WeightedRandom build() {
+ if (this.entries.isEmpty()) {
+ throw new IllegalStateException("No entries added!");
+ }
+
+ return new WeightedRandom(this);
+ }
+ }
+}
diff --git a/orebfuscator-plugin/src/main/resources/config/config-1.16.yml b/orebfuscator-core/src/main/resources/config/config-1.16.0.yml
similarity index 57%
rename from orebfuscator-plugin/src/main/resources/config/config-1.16.yml
rename to orebfuscator-core/src/main/resources/config/config-1.16.0.yml
index aca8f0ea5..648d6ef6d 100644
--- a/orebfuscator-plugin/src/main/resources/config/config-1.16.yml
+++ b/orebfuscator-core/src/main/resources/config/config-1.16.0.yml
@@ -1,4 +1,4 @@
-version: 4
+version: 5
general:
checkForUpdates: true
updateOnBlockDamage: true
@@ -36,41 +36,27 @@ obfuscation:
- world
layerObfuscation: false
hiddenBlocks:
- - minecraft:chest
- - minecraft:ender_chest
- - minecraft:trapped_chest
- - minecraft:spawner
- - minecraft:emerald_ore
+ - tag(minecraft:base_stone_overworld)
+ - minecraft:bedrock
+ - minecraft:coal_ore
- minecraft:diamond_ore
+ - minecraft:emerald_ore
- minecraft:gold_ore
- minecraft:iron_ore
- - minecraft:coal_ore
- minecraft:lapis_ore
- minecraft:redstone_ore
- - minecraft:stone
- - minecraft:clay
- - minecraft:shulker_box
- - minecraft:white_shulker_box
- - minecraft:orange_shulker_box
- - minecraft:magenta_shulker_box
- - minecraft:light_blue_shulker_box
- - minecraft:yellow_shulker_box
- - minecraft:lime_shulker_box
- - minecraft:pink_shulker_box
- - minecraft:gray_shulker_box
- - minecraft:light_gray_shulker_box
- - minecraft:cyan_shulker_box
- - minecraft:purple_shulker_box
- - minecraft:blue_shulker_box
- - minecraft:brown_shulker_box
- - minecraft:green_shulker_box
- - minecraft:red_shulker_box
- - minecraft:black_shulker_box
- - minecraft:bedrock
+ - tag(minecraft:shulker_boxes)
+ - minecraft:barrel
+ - minecraft:chest
+ - minecraft:dispenser
+ - minecraft:dropper
+ - minecraft:ender_chest
+ - minecraft:hopper
+ - minecraft:trapped_chest
randomBlocks:
section-global:
minY: -2032
- maxY: 2032
+ maxY: 2031
blocks:
minecraft:cave_air: 5
minecraft:cobblestone: 1
@@ -97,39 +83,24 @@ obfuscation:
- world_nether
layerObfuscation: false
hiddenBlocks:
+ - tag(minecraft:base_stone_nether)
+ - tag(minecraft:soul_fire_base_blocks)
+ - minecraft:ancient_debris
+ - minecraft:bedrock
+ - minecraft:nether_gold_ore
+ - minecraft:nether_quartz_ore
+ - tag(minecraft:shulker_boxes)
+ - minecraft:barrel
- minecraft:chest
+ - minecraft:dispenser
+ - minecraft:dropper
- minecraft:ender_chest
+ - minecraft:hopper
- minecraft:trapped_chest
- - minecraft:spawner
- - minecraft:netherrack
- - minecraft:nether_quartz_ore
- - minecraft:shulker_box
- - minecraft:white_shulker_box
- - minecraft:orange_shulker_box
- - minecraft:magenta_shulker_box
- - minecraft:light_blue_shulker_box
- - minecraft:yellow_shulker_box
- - minecraft:lime_shulker_box
- - minecraft:pink_shulker_box
- - minecraft:gray_shulker_box
- - minecraft:light_gray_shulker_box
- - minecraft:cyan_shulker_box
- - minecraft:purple_shulker_box
- - minecraft:blue_shulker_box
- - minecraft:brown_shulker_box
- - minecraft:green_shulker_box
- - minecraft:red_shulker_box
- - minecraft:black_shulker_box
- - minecraft:ancient_debris
- - minecraft:nether_gold_ore
- - minecraft:basalt
- - minecraft:soul_sand
- - minecraft:soul_soil
- - minecraft:bedrock
randomBlocks:
section-global:
minY: -2032
- maxY: 2032
+ maxY: 2031
blocks:
minecraft:netherrack: 4
minecraft:nether_quartz_ore: 1
@@ -153,31 +124,19 @@ obfuscation:
- world_the_end
layerObfuscation: false
hiddenBlocks:
+ - minecraft:end_stone
+ - tag(minecraft:shulker_boxes)
+ - minecraft:barrel
- minecraft:chest
+ - minecraft:dispenser
+ - minecraft:dropper
- minecraft:ender_chest
+ - minecraft:hopper
- minecraft:trapped_chest
- - minecraft:spawner
- - minecraft:shulker_box
- - minecraft:white_shulker_box
- - minecraft:orange_shulker_box
- - minecraft:magenta_shulker_box
- - minecraft:light_blue_shulker_box
- - minecraft:yellow_shulker_box
- - minecraft:lime_shulker_box
- - minecraft:pink_shulker_box
- - minecraft:gray_shulker_box
- - minecraft:light_gray_shulker_box
- - minecraft:cyan_shulker_box
- - minecraft:purple_shulker_box
- - minecraft:blue_shulker_box
- - minecraft:brown_shulker_box
- - minecraft:green_shulker_box
- - minecraft:red_shulker_box
- - minecraft:black_shulker_box
randomBlocks:
section-global:
minY: -2032
- maxY: 2032
+ maxY: 2031
blocks:
minecraft:end_stone: 1
minecraft:end_stone_bricks: 1
@@ -187,190 +146,155 @@ obfuscation:
proximity:
proximity-overworld:
enabled: true
- minY: 0
+ minY: -2032
maxY: 2031
worlds:
- world
distance: 24
frustumCulling:
enabled: true
- minDistance: 3
- fov: 80
+ minDistance: 3.0
+ fov: 80.0
rayCastCheck:
enabled: false
onlyCheckCenter: false
useBlockBelow: true
hiddenBlocks:
- minecraft:mossy_cobblestone: {}
minecraft:diamond_ore: {}
minecraft:emerald_ore: {}
- minecraft:chest: {}
- minecraft:ender_chest: {}
- minecraft:trapped_chest: {}
- minecraft:anvil: {}
- minecraft:crafting_table: {}
- minecraft:dispenser: {}
- minecraft:enchanting_table: {}
- minecraft:furnace: {}
+ minecraft:mossy_cobblestone: {}
+ tag(minecraft:anvil): {}
+ tag(minecraft:beehives): {}
+ tag(minecraft:beds): {}
minecraft:blast_furnace: {}
+ minecraft:brewing_stand: {}
minecraft:cartography_table: {}
+ minecraft:cauldron: {}
+ minecraft:composter: {}
+ minecraft:crafting_table: {}
+ minecraft:enchanting_table: {}
minecraft:fletching_table: {}
+ minecraft:furnace: {}
minecraft:grindstone: {}
- minecraft:composter: {}
minecraft:lectern: {}
minecraft:loom: {}
minecraft:smithing_table: {}
minecraft:smoker: {}
minecraft:stonecutter: {}
- minecraft:hopper: {}
minecraft:spawner: {}
- minecraft:shulker_box: {}
- minecraft:white_shulker_box: {}
- minecraft:orange_shulker_box: {}
- minecraft:magenta_shulker_box: {}
- minecraft:light_blue_shulker_box: {}
- minecraft:yellow_shulker_box: {}
- minecraft:lime_shulker_box: {}
- minecraft:pink_shulker_box: {}
- minecraft:gray_shulker_box: {}
- minecraft:light_gray_shulker_box: {}
- minecraft:cyan_shulker_box: {}
- minecraft:purple_shulker_box: {}
- minecraft:blue_shulker_box: {}
- minecraft:brown_shulker_box: {}
- minecraft:green_shulker_box: {}
- minecraft:red_shulker_box: {}
- minecraft:black_shulker_box: {}
- minecraft:bee_nest: {}
- minecraft:beehive: {}
+ tag(minecraft:shulker_boxes): {}
+ minecraft:barrel: {}
+ minecraft:chest: {}
+ minecraft:dispenser: {}
+ minecraft:dropper: {}
+ minecraft:ender_chest: {}
+ minecraft:hopper: {}
+ minecraft:trapped_chest: {}
randomBlocks:
section-global:
minY: -2032
- maxY: 2032
+ maxY: 2031
blocks:
minecraft:stone: 1
proximity-nether:
enabled: true
- minY: 0
+ minY: -2032
maxY: 2031
worlds:
- world_nether
distance: 24
frustumCulling:
enabled: true
- minDistance: 3
- fov: 80
+ minDistance: 3.0
+ fov: 80.0
rayCastCheck:
enabled: false
onlyCheckCenter: false
useBlockBelow: true
hiddenBlocks:
- minecraft:chest: {}
- minecraft:ender_chest: {}
- minecraft:trapped_chest: {}
- minecraft:anvil: {}
- minecraft:crafting_table: {}
- minecraft:dispenser: {}
- minecraft:enchanting_table: {}
- minecraft:furnace: {}
+ minecraft:ancient_debris: {}
+ minecraft:lodestone: {}
+ minecraft:nether_gold_ore: {}
+ minecraft:respawn_anchor: {}
+ tag(minecraft:anvil): {}
+ tag(minecraft:beehives): {}
+ tag(minecraft:beds): {}
minecraft:blast_furnace: {}
+ minecraft:brewing_stand: {}
minecraft:cartography_table: {}
+ minecraft:cauldron: {}
+ minecraft:composter: {}
+ minecraft:crafting_table: {}
+ minecraft:enchanting_table: {}
minecraft:fletching_table: {}
+ minecraft:furnace: {}
minecraft:grindstone: {}
- minecraft:composter: {}
minecraft:lectern: {}
minecraft:loom: {}
minecraft:smithing_table: {}
minecraft:smoker: {}
minecraft:stonecutter: {}
- minecraft:hopper: {}
minecraft:spawner: {}
- minecraft:shulker_box: {}
- minecraft:white_shulker_box: {}
- minecraft:orange_shulker_box: {}
- minecraft:magenta_shulker_box: {}
- minecraft:light_blue_shulker_box: {}
- minecraft:yellow_shulker_box: {}
- minecraft:lime_shulker_box: {}
- minecraft:pink_shulker_box: {}
- minecraft:gray_shulker_box: {}
- minecraft:light_gray_shulker_box: {}
- minecraft:cyan_shulker_box: {}
- minecraft:purple_shulker_box: {}
- minecraft:blue_shulker_box: {}
- minecraft:brown_shulker_box: {}
- minecraft:green_shulker_box: {}
- minecraft:red_shulker_box: {}
- minecraft:black_shulker_box: {}
- minecraft:bee_nest: {}
- minecraft:beehive: {}
- minecraft:ancient_debris: {}
- minecraft:nether_gold_ore: {}
- minecraft:respawn_anchor: {}
+ tag(minecraft:shulker_boxes): {}
+ minecraft:barrel: {}
+ minecraft:chest: {}
+ minecraft:dispenser: {}
+ minecraft:dropper: {}
+ minecraft:ender_chest: {}
+ minecraft:hopper: {}
+ minecraft:trapped_chest: {}
randomBlocks:
section-global:
minY: -2032
- maxY: 2032
+ maxY: 2031
blocks:
minecraft:netherrack: 1
proximity-end:
enabled: true
- minY: 0
+ minY: -2032
maxY: 2031
worlds:
- world_the_end
distance: 24
frustumCulling:
enabled: true
- minDistance: 3
- fov: 80
+ minDistance: 3.0
+ fov: 80.0
rayCastCheck:
enabled: false
onlyCheckCenter: false
useBlockBelow: true
hiddenBlocks:
- minecraft:chest: {}
- minecraft:ender_chest: {}
- minecraft:trapped_chest: {}
- minecraft:anvil: {}
- minecraft:crafting_table: {}
- minecraft:dispenser: {}
- minecraft:enchanting_table: {}
- minecraft:furnace: {}
+ tag(minecraft:anvil): {}
+ tag(minecraft:beehives): {}
+ tag(minecraft:beds): {}
minecraft:blast_furnace: {}
+ minecraft:brewing_stand: {}
minecraft:cartography_table: {}
+ minecraft:cauldron: {}
+ minecraft:composter: {}
+ minecraft:crafting_table: {}
+ minecraft:enchanting_table: {}
minecraft:fletching_table: {}
+ minecraft:furnace: {}
minecraft:grindstone: {}
- minecraft:composter: {}
minecraft:lectern: {}
minecraft:loom: {}
minecraft:smithing_table: {}
minecraft:smoker: {}
minecraft:stonecutter: {}
- minecraft:hopper: {}
minecraft:spawner: {}
- minecraft:shulker_box: {}
- minecraft:white_shulker_box: {}
- minecraft:orange_shulker_box: {}
- minecraft:magenta_shulker_box: {}
- minecraft:light_blue_shulker_box: {}
- minecraft:yellow_shulker_box: {}
- minecraft:lime_shulker_box: {}
- minecraft:pink_shulker_box: {}
- minecraft:gray_shulker_box: {}
- minecraft:light_gray_shulker_box: {}
- minecraft:cyan_shulker_box: {}
- minecraft:purple_shulker_box: {}
- minecraft:blue_shulker_box: {}
- minecraft:brown_shulker_box: {}
- minecraft:green_shulker_box: {}
- minecraft:red_shulker_box: {}
- minecraft:black_shulker_box: {}
- minecraft:bee_nest: {}
- minecraft:beehive: {}
+ tag(minecraft:shulker_boxes): {}
+ minecraft:barrel: {}
+ minecraft:chest: {}
+ minecraft:dispenser: {}
+ minecraft:dropper: {}
+ minecraft:ender_chest: {}
+ minecraft:hopper: {}
+ minecraft:trapped_chest: {}
randomBlocks:
section-global:
minY: -2032
- maxY: 2032
+ maxY: 2031
blocks:
minecraft:end_stone: 1
diff --git a/orebfuscator-plugin/src/main/resources/config/config-1.20.yml b/orebfuscator-core/src/main/resources/config/config-1.17.0.yml
similarity index 57%
rename from orebfuscator-plugin/src/main/resources/config/config-1.20.yml
rename to orebfuscator-core/src/main/resources/config/config-1.17.0.yml
index 02455a163..913da5494 100644
--- a/orebfuscator-plugin/src/main/resources/config/config-1.20.yml
+++ b/orebfuscator-core/src/main/resources/config/config-1.17.0.yml
@@ -1,4 +1,4 @@
-version: 4
+version: 5
general:
checkForUpdates: true
updateOnBlockDamage: true
@@ -36,48 +36,27 @@ obfuscation:
- world
layerObfuscation: false
hiddenBlocks:
- - minecraft:emerald_ore
- - minecraft:deepslate_emerald_ore
- - minecraft:diamond_ore
- - minecraft:deepslate_diamond_ore
- - minecraft:gold_ore
- - minecraft:deepslate_gold_ore
+ - tag(minecraft:base_stone_overworld)
+ - tag(minecraft:coal_ores)
+ - tag(minecraft:copper_ores)
+ - tag(minecraft:diamond_ores)
+ - tag(minecraft:emerald_ores)
+ - tag(minecraft:gold_ores)
+ - tag(minecraft:iron_ores)
+ - tag(minecraft:lapis_ores)
+ - tag(minecraft:redstone_ores)
+ - minecraft:bedrock
+ - minecraft:raw_copper_block
- minecraft:raw_gold_block
- - minecraft:iron_ore
- - minecraft:deepslate_iron_ore
- minecraft:raw_iron_block
- - minecraft:coal_ore
- - minecraft:deepslate_coal_ore
- - minecraft:lapis_ore
- - minecraft:deepslate_lapis_ore
- - minecraft:redstone_ore
- - minecraft:deepslate_redstone_ore
- - minecraft:copper_ore
- - minecraft:raw_copper_block
- - minecraft:deepslate_copper_ore
- - minecraft:stone
- - minecraft:deepslate
+ - tag(minecraft:shulker_boxes)
+ - minecraft:barrel
- minecraft:chest
+ - minecraft:dispenser
+ - minecraft:dropper
- minecraft:ender_chest
+ - minecraft:hopper
- minecraft:trapped_chest
- - minecraft:shulker_box
- - minecraft:white_shulker_box
- - minecraft:orange_shulker_box
- - minecraft:magenta_shulker_box
- - minecraft:light_blue_shulker_box
- - minecraft:yellow_shulker_box
- - minecraft:lime_shulker_box
- - minecraft:pink_shulker_box
- - minecraft:gray_shulker_box
- - minecraft:light_gray_shulker_box
- - minecraft:cyan_shulker_box
- - minecraft:purple_shulker_box
- - minecraft:blue_shulker_box
- - minecraft:brown_shulker_box
- - minecraft:green_shulker_box
- - minecraft:red_shulker_box
- - minecraft:black_shulker_box
- - minecraft:bedrock
randomBlocks:
section-global:
minY: -2032
@@ -114,8 +93,8 @@ obfuscation:
minecraft:deepslate_lapis_ore: 5
minecraft:deepslate_redstone_ore: 5
section-bedrock:
- minY: -64
- maxY: -60
+ minY: 0
+ maxY: 5
blocks:
minecraft:bedrock: 20
obfuscation-nether:
@@ -126,38 +105,24 @@ obfuscation:
- world_nether
layerObfuscation: false
hiddenBlocks:
- - minecraft:netherrack
+ - tag(minecraft:base_stone_nether)
+ - tag(minecraft:soul_fire_base_blocks)
+ - minecraft:ancient_debris
+ - minecraft:bedrock
+ - minecraft:nether_gold_ore
- minecraft:nether_quartz_ore
+ - tag(minecraft:shulker_boxes)
+ - minecraft:barrel
- minecraft:chest
+ - minecraft:dispenser
+ - minecraft:dropper
- minecraft:ender_chest
+ - minecraft:hopper
- minecraft:trapped_chest
- - minecraft:shulker_box
- - minecraft:white_shulker_box
- - minecraft:orange_shulker_box
- - minecraft:magenta_shulker_box
- - minecraft:light_blue_shulker_box
- - minecraft:yellow_shulker_box
- - minecraft:lime_shulker_box
- - minecraft:pink_shulker_box
- - minecraft:gray_shulker_box
- - minecraft:light_gray_shulker_box
- - minecraft:cyan_shulker_box
- - minecraft:purple_shulker_box
- - minecraft:blue_shulker_box
- - minecraft:brown_shulker_box
- - minecraft:green_shulker_box
- - minecraft:red_shulker_box
- - minecraft:black_shulker_box
- - minecraft:ancient_debris
- - minecraft:nether_gold_ore
- - minecraft:basalt
- - minecraft:soul_sand
- - minecraft:soul_soil
- - minecraft:bedrock
randomBlocks:
section-global:
minY: -2032
- maxY: 2032
+ maxY: 2031
blocks:
minecraft:netherrack: 4
minecraft:nether_quartz_ore: 1
@@ -181,30 +146,19 @@ obfuscation:
- world_the_end
layerObfuscation: false
hiddenBlocks:
+ - minecraft:end_stone
+ - tag(minecraft:shulker_boxes)
+ - minecraft:barrel
- minecraft:chest
+ - minecraft:dispenser
+ - minecraft:dropper
- minecraft:ender_chest
+ - minecraft:hopper
- minecraft:trapped_chest
- - minecraft:shulker_box
- - minecraft:white_shulker_box
- - minecraft:orange_shulker_box
- - minecraft:magenta_shulker_box
- - minecraft:light_blue_shulker_box
- - minecraft:yellow_shulker_box
- - minecraft:lime_shulker_box
- - minecraft:pink_shulker_box
- - minecraft:gray_shulker_box
- - minecraft:light_gray_shulker_box
- - minecraft:cyan_shulker_box
- - minecraft:purple_shulker_box
- - minecraft:blue_shulker_box
- - minecraft:brown_shulker_box
- - minecraft:green_shulker_box
- - minecraft:red_shulker_box
- - minecraft:black_shulker_box
randomBlocks:
section-global:
minY: -2032
- maxY: 2032
+ maxY: 2031
blocks:
minecraft:end_stone: 1
minecraft:end_stone_bricks: 1
@@ -228,50 +182,36 @@ proximity:
onlyCheckCenter: false
useBlockBelow: true
hiddenBlocks:
+ tag(minecraft:diamond_ores): {}
+ tag(minecraft:emerald_ores): {}
minecraft:mossy_cobblestone: {}
- minecraft:diamond_ore: {}
- minecraft:deepslate_diamond_ore: {}
- minecraft:emerald_ore: {}
- minecraft:deepslate_emerald_ore: {}
- minecraft:chest: {}
- minecraft:ender_chest: {}
- minecraft:trapped_chest: {}
- minecraft:anvil: {}
- minecraft:crafting_table: {}
- minecraft:dispenser: {}
- minecraft:enchanting_table: {}
- minecraft:furnace: {}
+ tag(minecraft:anvil): {}
+ tag(minecraft:beehives): {}
+ tag(minecraft:beds): {}
+ tag(minecraft:cauldrons): {}
minecraft:blast_furnace: {}
+ minecraft:brewing_stand: {}
minecraft:cartography_table: {}
+ minecraft:composter: {}
+ minecraft:crafting_table: {}
+ minecraft:enchanting_table: {}
minecraft:fletching_table: {}
+ minecraft:furnace: {}
minecraft:grindstone: {}
- minecraft:composter: {}
minecraft:lectern: {}
minecraft:loom: {}
minecraft:smithing_table: {}
minecraft:smoker: {}
minecraft:stonecutter: {}
- minecraft:hopper: {}
minecraft:spawner: {}
- minecraft:shulker_box: {}
- minecraft:white_shulker_box: {}
- minecraft:orange_shulker_box: {}
- minecraft:magenta_shulker_box: {}
- minecraft:light_blue_shulker_box: {}
- minecraft:yellow_shulker_box: {}
- minecraft:lime_shulker_box: {}
- minecraft:pink_shulker_box: {}
- minecraft:gray_shulker_box: {}
- minecraft:light_gray_shulker_box: {}
- minecraft:cyan_shulker_box: {}
- minecraft:purple_shulker_box: {}
- minecraft:blue_shulker_box: {}
- minecraft:brown_shulker_box: {}
- minecraft:green_shulker_box: {}
- minecraft:red_shulker_box: {}
- minecraft:black_shulker_box: {}
- minecraft:bee_nest: {}
- minecraft:beehive: {}
+ tag(minecraft:shulker_boxes): {}
+ minecraft:barrel: {}
+ minecraft:chest: {}
+ minecraft:dispenser: {}
+ minecraft:dropper: {}
+ minecraft:ender_chest: {}
+ minecraft:hopper: {}
+ minecraft:trapped_chest: {}
randomBlocks:
section-stone:
minY: -5
@@ -299,52 +239,41 @@ proximity:
onlyCheckCenter: false
useBlockBelow: true
hiddenBlocks:
- minecraft:chest: {}
- minecraft:ender_chest: {}
- minecraft:trapped_chest: {}
- minecraft:anvil: {}
- minecraft:crafting_table: {}
- minecraft:dispenser: {}
- minecraft:enchanting_table: {}
- minecraft:furnace: {}
+ minecraft:ancient_debris: {}
+ minecraft:lodestone: {}
+ minecraft:nether_gold_ore: {}
+ minecraft:respawn_anchor: {}
+ tag(minecraft:anvil): {}
+ tag(minecraft:beehives): {}
+ tag(minecraft:beds): {}
+ tag(minecraft:cauldrons): {}
minecraft:blast_furnace: {}
+ minecraft:brewing_stand: {}
minecraft:cartography_table: {}
+ minecraft:composter: {}
+ minecraft:crafting_table: {}
+ minecraft:enchanting_table: {}
minecraft:fletching_table: {}
+ minecraft:furnace: {}
minecraft:grindstone: {}
- minecraft:composter: {}
minecraft:lectern: {}
minecraft:loom: {}
minecraft:smithing_table: {}
minecraft:smoker: {}
minecraft:stonecutter: {}
- minecraft:hopper: {}
minecraft:spawner: {}
- minecraft:shulker_box: {}
- minecraft:white_shulker_box: {}
- minecraft:orange_shulker_box: {}
- minecraft:magenta_shulker_box: {}
- minecraft:light_blue_shulker_box: {}
- minecraft:yellow_shulker_box: {}
- minecraft:lime_shulker_box: {}
- minecraft:pink_shulker_box: {}
- minecraft:gray_shulker_box: {}
- minecraft:light_gray_shulker_box: {}
- minecraft:cyan_shulker_box: {}
- minecraft:purple_shulker_box: {}
- minecraft:blue_shulker_box: {}
- minecraft:brown_shulker_box: {}
- minecraft:green_shulker_box: {}
- minecraft:red_shulker_box: {}
- minecraft:black_shulker_box: {}
- minecraft:bee_nest: {}
- minecraft:beehive: {}
- minecraft:ancient_debris: {}
- minecraft:nether_gold_ore: {}
- minecraft:respawn_anchor: {}
+ tag(minecraft:shulker_boxes): {}
+ minecraft:barrel: {}
+ minecraft:chest: {}
+ minecraft:dispenser: {}
+ minecraft:dropper: {}
+ minecraft:ender_chest: {}
+ minecraft:hopper: {}
+ minecraft:trapped_chest: {}
randomBlocks:
section-global:
minY: -2032
- maxY: 2032
+ maxY: 2031
blocks:
minecraft:netherrack: 1
proximity-end:
@@ -363,48 +292,36 @@ proximity:
onlyCheckCenter: false
useBlockBelow: true
hiddenBlocks:
- minecraft:chest: {}
- minecraft:ender_chest: {}
- minecraft:trapped_chest: {}
- minecraft:anvil: {}
- minecraft:crafting_table: {}
- minecraft:dispenser: {}
- minecraft:enchanting_table: {}
- minecraft:furnace: {}
+ tag(minecraft:anvil): {}
+ tag(minecraft:beehives): {}
+ tag(minecraft:beds): {}
+ tag(minecraft:cauldrons): {}
minecraft:blast_furnace: {}
+ minecraft:brewing_stand: {}
minecraft:cartography_table: {}
+ minecraft:composter: {}
+ minecraft:crafting_table: {}
+ minecraft:enchanting_table: {}
minecraft:fletching_table: {}
+ minecraft:furnace: {}
minecraft:grindstone: {}
- minecraft:composter: {}
minecraft:lectern: {}
minecraft:loom: {}
minecraft:smithing_table: {}
minecraft:smoker: {}
minecraft:stonecutter: {}
- minecraft:hopper: {}
minecraft:spawner: {}
- minecraft:shulker_box: {}
- minecraft:white_shulker_box: {}
- minecraft:orange_shulker_box: {}
- minecraft:magenta_shulker_box: {}
- minecraft:light_blue_shulker_box: {}
- minecraft:yellow_shulker_box: {}
- minecraft:lime_shulker_box: {}
- minecraft:pink_shulker_box: {}
- minecraft:gray_shulker_box: {}
- minecraft:light_gray_shulker_box: {}
- minecraft:cyan_shulker_box: {}
- minecraft:purple_shulker_box: {}
- minecraft:blue_shulker_box: {}
- minecraft:brown_shulker_box: {}
- minecraft:green_shulker_box: {}
- minecraft:red_shulker_box: {}
- minecraft:black_shulker_box: {}
- minecraft:bee_nest: {}
- minecraft:beehive: {}
+ tag(minecraft:shulker_boxes): {}
+ minecraft:barrel: {}
+ minecraft:chest: {}
+ minecraft:dispenser: {}
+ minecraft:dropper: {}
+ minecraft:ender_chest: {}
+ minecraft:hopper: {}
+ minecraft:trapped_chest: {}
randomBlocks:
section-global:
minY: -2032
- maxY: 2032
+ maxY: 2031
blocks:
minecraft:end_stone: 1
diff --git a/orebfuscator-plugin/src/main/resources/config/config-1.19.yml b/orebfuscator-core/src/main/resources/config/config-1.18.0.yml
similarity index 58%
rename from orebfuscator-plugin/src/main/resources/config/config-1.19.yml
rename to orebfuscator-core/src/main/resources/config/config-1.18.0.yml
index 02455a163..221c5b5f3 100644
--- a/orebfuscator-plugin/src/main/resources/config/config-1.19.yml
+++ b/orebfuscator-core/src/main/resources/config/config-1.18.0.yml
@@ -1,4 +1,4 @@
-version: 4
+version: 5
general:
checkForUpdates: true
updateOnBlockDamage: true
@@ -36,48 +36,27 @@ obfuscation:
- world
layerObfuscation: false
hiddenBlocks:
- - minecraft:emerald_ore
- - minecraft:deepslate_emerald_ore
- - minecraft:diamond_ore
- - minecraft:deepslate_diamond_ore
- - minecraft:gold_ore
- - minecraft:deepslate_gold_ore
+ - tag(minecraft:base_stone_overworld)
+ - tag(minecraft:coal_ores)
+ - tag(minecraft:copper_ores)
+ - tag(minecraft:diamond_ores)
+ - tag(minecraft:emerald_ores)
+ - tag(minecraft:gold_ores)
+ - tag(minecraft:iron_ores)
+ - tag(minecraft:lapis_ores)
+ - tag(minecraft:redstone_ores)
+ - minecraft:bedrock
+ - minecraft:raw_copper_block
- minecraft:raw_gold_block
- - minecraft:iron_ore
- - minecraft:deepslate_iron_ore
- minecraft:raw_iron_block
- - minecraft:coal_ore
- - minecraft:deepslate_coal_ore
- - minecraft:lapis_ore
- - minecraft:deepslate_lapis_ore
- - minecraft:redstone_ore
- - minecraft:deepslate_redstone_ore
- - minecraft:copper_ore
- - minecraft:raw_copper_block
- - minecraft:deepslate_copper_ore
- - minecraft:stone
- - minecraft:deepslate
+ - tag(minecraft:shulker_boxes)
+ - minecraft:barrel
- minecraft:chest
+ - minecraft:dispenser
+ - minecraft:dropper
- minecraft:ender_chest
+ - minecraft:hopper
- minecraft:trapped_chest
- - minecraft:shulker_box
- - minecraft:white_shulker_box
- - minecraft:orange_shulker_box
- - minecraft:magenta_shulker_box
- - minecraft:light_blue_shulker_box
- - minecraft:yellow_shulker_box
- - minecraft:lime_shulker_box
- - minecraft:pink_shulker_box
- - minecraft:gray_shulker_box
- - minecraft:light_gray_shulker_box
- - minecraft:cyan_shulker_box
- - minecraft:purple_shulker_box
- - minecraft:blue_shulker_box
- - minecraft:brown_shulker_box
- - minecraft:green_shulker_box
- - minecraft:red_shulker_box
- - minecraft:black_shulker_box
- - minecraft:bedrock
randomBlocks:
section-global:
minY: -2032
@@ -126,38 +105,24 @@ obfuscation:
- world_nether
layerObfuscation: false
hiddenBlocks:
- - minecraft:netherrack
+ - tag(minecraft:base_stone_nether)
+ - tag(minecraft:soul_fire_base_blocks)
+ - minecraft:ancient_debris
+ - minecraft:bedrock
+ - minecraft:nether_gold_ore
- minecraft:nether_quartz_ore
+ - tag(minecraft:shulker_boxes)
+ - minecraft:barrel
- minecraft:chest
+ - minecraft:dispenser
+ - minecraft:dropper
- minecraft:ender_chest
+ - minecraft:hopper
- minecraft:trapped_chest
- - minecraft:shulker_box
- - minecraft:white_shulker_box
- - minecraft:orange_shulker_box
- - minecraft:magenta_shulker_box
- - minecraft:light_blue_shulker_box
- - minecraft:yellow_shulker_box
- - minecraft:lime_shulker_box
- - minecraft:pink_shulker_box
- - minecraft:gray_shulker_box
- - minecraft:light_gray_shulker_box
- - minecraft:cyan_shulker_box
- - minecraft:purple_shulker_box
- - minecraft:blue_shulker_box
- - minecraft:brown_shulker_box
- - minecraft:green_shulker_box
- - minecraft:red_shulker_box
- - minecraft:black_shulker_box
- - minecraft:ancient_debris
- - minecraft:nether_gold_ore
- - minecraft:basalt
- - minecraft:soul_sand
- - minecraft:soul_soil
- - minecraft:bedrock
randomBlocks:
section-global:
minY: -2032
- maxY: 2032
+ maxY: 2031
blocks:
minecraft:netherrack: 4
minecraft:nether_quartz_ore: 1
@@ -181,30 +146,19 @@ obfuscation:
- world_the_end
layerObfuscation: false
hiddenBlocks:
+ - minecraft:end_stone
+ - tag(minecraft:shulker_boxes)
+ - minecraft:barrel
- minecraft:chest
+ - minecraft:dispenser
+ - minecraft:dropper
- minecraft:ender_chest
+ - minecraft:hopper
- minecraft:trapped_chest
- - minecraft:shulker_box
- - minecraft:white_shulker_box
- - minecraft:orange_shulker_box
- - minecraft:magenta_shulker_box
- - minecraft:light_blue_shulker_box
- - minecraft:yellow_shulker_box
- - minecraft:lime_shulker_box
- - minecraft:pink_shulker_box
- - minecraft:gray_shulker_box
- - minecraft:light_gray_shulker_box
- - minecraft:cyan_shulker_box
- - minecraft:purple_shulker_box
- - minecraft:blue_shulker_box
- - minecraft:brown_shulker_box
- - minecraft:green_shulker_box
- - minecraft:red_shulker_box
- - minecraft:black_shulker_box
randomBlocks:
section-global:
minY: -2032
- maxY: 2032
+ maxY: 2031
blocks:
minecraft:end_stone: 1
minecraft:end_stone_bricks: 1
@@ -228,50 +182,36 @@ proximity:
onlyCheckCenter: false
useBlockBelow: true
hiddenBlocks:
+ tag(minecraft:diamond_ores): {}
+ tag(minecraft:emerald_ores): {}
minecraft:mossy_cobblestone: {}
- minecraft:diamond_ore: {}
- minecraft:deepslate_diamond_ore: {}
- minecraft:emerald_ore: {}
- minecraft:deepslate_emerald_ore: {}
- minecraft:chest: {}
- minecraft:ender_chest: {}
- minecraft:trapped_chest: {}
- minecraft:anvil: {}
- minecraft:crafting_table: {}
- minecraft:dispenser: {}
- minecraft:enchanting_table: {}
- minecraft:furnace: {}
+ tag(minecraft:anvil): {}
+ tag(minecraft:beehives): {}
+ tag(minecraft:beds): {}
+ tag(minecraft:cauldrons): {}
minecraft:blast_furnace: {}
+ minecraft:brewing_stand: {}
minecraft:cartography_table: {}
+ minecraft:composter: {}
+ minecraft:crafting_table: {}
+ minecraft:enchanting_table: {}
minecraft:fletching_table: {}
+ minecraft:furnace: {}
minecraft:grindstone: {}
- minecraft:composter: {}
minecraft:lectern: {}
minecraft:loom: {}
minecraft:smithing_table: {}
minecraft:smoker: {}
minecraft:stonecutter: {}
- minecraft:hopper: {}
minecraft:spawner: {}
- minecraft:shulker_box: {}
- minecraft:white_shulker_box: {}
- minecraft:orange_shulker_box: {}
- minecraft:magenta_shulker_box: {}
- minecraft:light_blue_shulker_box: {}
- minecraft:yellow_shulker_box: {}
- minecraft:lime_shulker_box: {}
- minecraft:pink_shulker_box: {}
- minecraft:gray_shulker_box: {}
- minecraft:light_gray_shulker_box: {}
- minecraft:cyan_shulker_box: {}
- minecraft:purple_shulker_box: {}
- minecraft:blue_shulker_box: {}
- minecraft:brown_shulker_box: {}
- minecraft:green_shulker_box: {}
- minecraft:red_shulker_box: {}
- minecraft:black_shulker_box: {}
- minecraft:bee_nest: {}
- minecraft:beehive: {}
+ tag(minecraft:shulker_boxes): {}
+ minecraft:barrel: {}
+ minecraft:chest: {}
+ minecraft:dispenser: {}
+ minecraft:dropper: {}
+ minecraft:ender_chest: {}
+ minecraft:hopper: {}
+ minecraft:trapped_chest: {}
randomBlocks:
section-stone:
minY: -5
@@ -299,52 +239,41 @@ proximity:
onlyCheckCenter: false
useBlockBelow: true
hiddenBlocks:
- minecraft:chest: {}
- minecraft:ender_chest: {}
- minecraft:trapped_chest: {}
- minecraft:anvil: {}
- minecraft:crafting_table: {}
- minecraft:dispenser: {}
- minecraft:enchanting_table: {}
- minecraft:furnace: {}
+ minecraft:ancient_debris: {}
+ minecraft:lodestone: {}
+ minecraft:nether_gold_ore: {}
+ minecraft:respawn_anchor: {}
+ tag(minecraft:anvil): {}
+ tag(minecraft:beehives): {}
+ tag(minecraft:beds): {}
+ tag(minecraft:cauldrons): {}
minecraft:blast_furnace: {}
+ minecraft:brewing_stand: {}
minecraft:cartography_table: {}
+ minecraft:composter: {}
+ minecraft:crafting_table: {}
+ minecraft:enchanting_table: {}
minecraft:fletching_table: {}
+ minecraft:furnace: {}
minecraft:grindstone: {}
- minecraft:composter: {}
minecraft:lectern: {}
minecraft:loom: {}
minecraft:smithing_table: {}
minecraft:smoker: {}
minecraft:stonecutter: {}
- minecraft:hopper: {}
minecraft:spawner: {}
- minecraft:shulker_box: {}
- minecraft:white_shulker_box: {}
- minecraft:orange_shulker_box: {}
- minecraft:magenta_shulker_box: {}
- minecraft:light_blue_shulker_box: {}
- minecraft:yellow_shulker_box: {}
- minecraft:lime_shulker_box: {}
- minecraft:pink_shulker_box: {}
- minecraft:gray_shulker_box: {}
- minecraft:light_gray_shulker_box: {}
- minecraft:cyan_shulker_box: {}
- minecraft:purple_shulker_box: {}
- minecraft:blue_shulker_box: {}
- minecraft:brown_shulker_box: {}
- minecraft:green_shulker_box: {}
- minecraft:red_shulker_box: {}
- minecraft:black_shulker_box: {}
- minecraft:bee_nest: {}
- minecraft:beehive: {}
- minecraft:ancient_debris: {}
- minecraft:nether_gold_ore: {}
- minecraft:respawn_anchor: {}
+ tag(minecraft:shulker_boxes): {}
+ minecraft:barrel: {}
+ minecraft:chest: {}
+ minecraft:dispenser: {}
+ minecraft:dropper: {}
+ minecraft:ender_chest: {}
+ minecraft:hopper: {}
+ minecraft:trapped_chest: {}
randomBlocks:
section-global:
minY: -2032
- maxY: 2032
+ maxY: 2031
blocks:
minecraft:netherrack: 1
proximity-end:
@@ -363,48 +292,36 @@ proximity:
onlyCheckCenter: false
useBlockBelow: true
hiddenBlocks:
- minecraft:chest: {}
- minecraft:ender_chest: {}
- minecraft:trapped_chest: {}
- minecraft:anvil: {}
- minecraft:crafting_table: {}
- minecraft:dispenser: {}
- minecraft:enchanting_table: {}
- minecraft:furnace: {}
+ tag(minecraft:anvil): {}
+ tag(minecraft:beehives): {}
+ tag(minecraft:beds): {}
+ tag(minecraft:cauldrons): {}
minecraft:blast_furnace: {}
+ minecraft:brewing_stand: {}
minecraft:cartography_table: {}
+ minecraft:composter: {}
+ minecraft:crafting_table: {}
+ minecraft:enchanting_table: {}
minecraft:fletching_table: {}
+ minecraft:furnace: {}
minecraft:grindstone: {}
- minecraft:composter: {}
minecraft:lectern: {}
minecraft:loom: {}
minecraft:smithing_table: {}
minecraft:smoker: {}
minecraft:stonecutter: {}
- minecraft:hopper: {}
minecraft:spawner: {}
- minecraft:shulker_box: {}
- minecraft:white_shulker_box: {}
- minecraft:orange_shulker_box: {}
- minecraft:magenta_shulker_box: {}
- minecraft:light_blue_shulker_box: {}
- minecraft:yellow_shulker_box: {}
- minecraft:lime_shulker_box: {}
- minecraft:pink_shulker_box: {}
- minecraft:gray_shulker_box: {}
- minecraft:light_gray_shulker_box: {}
- minecraft:cyan_shulker_box: {}
- minecraft:purple_shulker_box: {}
- minecraft:blue_shulker_box: {}
- minecraft:brown_shulker_box: {}
- minecraft:green_shulker_box: {}
- minecraft:red_shulker_box: {}
- minecraft:black_shulker_box: {}
- minecraft:bee_nest: {}
- minecraft:beehive: {}
+ tag(minecraft:shulker_boxes): {}
+ minecraft:barrel: {}
+ minecraft:chest: {}
+ minecraft:dispenser: {}
+ minecraft:dropper: {}
+ minecraft:ender_chest: {}
+ minecraft:hopper: {}
+ minecraft:trapped_chest: {}
randomBlocks:
section-global:
minY: -2032
- maxY: 2032
+ maxY: 2031
blocks:
minecraft:end_stone: 1
diff --git a/orebfuscator-plugin/src/main/resources/config/config-1.21.yml b/orebfuscator-core/src/main/resources/config/config-1.20.3.yml
similarity index 58%
rename from orebfuscator-plugin/src/main/resources/config/config-1.21.yml
rename to orebfuscator-core/src/main/resources/config/config-1.20.3.yml
index a66c3002b..b00acd8a6 100644
--- a/orebfuscator-plugin/src/main/resources/config/config-1.21.yml
+++ b/orebfuscator-core/src/main/resources/config/config-1.20.3.yml
@@ -1,4 +1,4 @@
-version: 4
+version: 5
general:
checkForUpdates: true
updateOnBlockDamage: true
@@ -36,48 +36,28 @@ obfuscation:
- world
layerObfuscation: false
hiddenBlocks:
- - minecraft:emerald_ore
- - minecraft:deepslate_emerald_ore
- - minecraft:diamond_ore
- - minecraft:deepslate_diamond_ore
- - minecraft:gold_ore
- - minecraft:deepslate_gold_ore
+ - tag(minecraft:base_stone_overworld)
+ - tag(minecraft:coal_ores)
+ - tag(minecraft:copper_ores)
+ - tag(minecraft:diamond_ores)
+ - tag(minecraft:emerald_ores)
+ - tag(minecraft:gold_ores)
+ - tag(minecraft:iron_ores)
+ - tag(minecraft:lapis_ores)
+ - tag(minecraft:redstone_ores)
+ - minecraft:bedrock
+ - minecraft:raw_copper_block
- minecraft:raw_gold_block
- - minecraft:iron_ore
- - minecraft:deepslate_iron_ore
- minecraft:raw_iron_block
- - minecraft:coal_ore
- - minecraft:deepslate_coal_ore
- - minecraft:lapis_ore
- - minecraft:deepslate_lapis_ore
- - minecraft:redstone_ore
- - minecraft:deepslate_redstone_ore
- - minecraft:copper_ore
- - minecraft:raw_copper_block
- - minecraft:deepslate_copper_ore
- - minecraft:stone
- - minecraft:deepslate
+ - tag(minecraft:shulker_boxes)
+ - minecraft:barrel
- minecraft:chest
+ - minecraft:crafter
+ - minecraft:dispenser
+ - minecraft:dropper
- minecraft:ender_chest
+ - minecraft:hopper
- minecraft:trapped_chest
- - minecraft:shulker_box
- - minecraft:white_shulker_box
- - minecraft:orange_shulker_box
- - minecraft:magenta_shulker_box
- - minecraft:light_blue_shulker_box
- - minecraft:yellow_shulker_box
- - minecraft:lime_shulker_box
- - minecraft:pink_shulker_box
- - minecraft:gray_shulker_box
- - minecraft:light_gray_shulker_box
- - minecraft:cyan_shulker_box
- - minecraft:purple_shulker_box
- - minecraft:blue_shulker_box
- - minecraft:brown_shulker_box
- - minecraft:green_shulker_box
- - minecraft:red_shulker_box
- - minecraft:black_shulker_box
- - minecraft:bedrock
randomBlocks:
section-global:
minY: -2032
@@ -126,38 +106,25 @@ obfuscation:
- world_nether
layerObfuscation: false
hiddenBlocks:
- - minecraft:netherrack
+ - tag(minecraft:base_stone_nether)
+ - tag(minecraft:soul_fire_base_blocks)
+ - minecraft:ancient_debris
+ - minecraft:bedrock
+ - minecraft:nether_gold_ore
- minecraft:nether_quartz_ore
+ - tag(minecraft:shulker_boxes)
+ - minecraft:barrel
- minecraft:chest
+ - minecraft:crafter
+ - minecraft:dispenser
+ - minecraft:dropper
- minecraft:ender_chest
+ - minecraft:hopper
- minecraft:trapped_chest
- - minecraft:shulker_box
- - minecraft:white_shulker_box
- - minecraft:orange_shulker_box
- - minecraft:magenta_shulker_box
- - minecraft:light_blue_shulker_box
- - minecraft:yellow_shulker_box
- - minecraft:lime_shulker_box
- - minecraft:pink_shulker_box
- - minecraft:gray_shulker_box
- - minecraft:light_gray_shulker_box
- - minecraft:cyan_shulker_box
- - minecraft:purple_shulker_box
- - minecraft:blue_shulker_box
- - minecraft:brown_shulker_box
- - minecraft:green_shulker_box
- - minecraft:red_shulker_box
- - minecraft:black_shulker_box
- - minecraft:ancient_debris
- - minecraft:nether_gold_ore
- - minecraft:basalt
- - minecraft:soul_sand
- - minecraft:soul_soil
- - minecraft:bedrock
randomBlocks:
section-global:
minY: -2032
- maxY: 2032
+ maxY: 2031
blocks:
minecraft:netherrack: 4
minecraft:nether_quartz_ore: 1
@@ -181,30 +148,20 @@ obfuscation:
- world_the_end
layerObfuscation: false
hiddenBlocks:
+ - minecraft:end_stone
+ - tag(minecraft:shulker_boxes)
+ - minecraft:barrel
- minecraft:chest
+ - minecraft:crafter
+ - minecraft:dispenser
+ - minecraft:dropper
- minecraft:ender_chest
+ - minecraft:hopper
- minecraft:trapped_chest
- - minecraft:shulker_box
- - minecraft:white_shulker_box
- - minecraft:orange_shulker_box
- - minecraft:magenta_shulker_box
- - minecraft:light_blue_shulker_box
- - minecraft:yellow_shulker_box
- - minecraft:lime_shulker_box
- - minecraft:pink_shulker_box
- - minecraft:gray_shulker_box
- - minecraft:light_gray_shulker_box
- - minecraft:cyan_shulker_box
- - minecraft:purple_shulker_box
- - minecraft:blue_shulker_box
- - minecraft:brown_shulker_box
- - minecraft:green_shulker_box
- - minecraft:red_shulker_box
- - minecraft:black_shulker_box
randomBlocks:
section-global:
minY: -2032
- maxY: 2032
+ maxY: 2031
blocks:
minecraft:end_stone: 1
minecraft:end_stone_bricks: 1
@@ -228,51 +185,37 @@ proximity:
onlyCheckCenter: false
useBlockBelow: true
hiddenBlocks:
+ tag(minecraft:diamond_ores): {}
+ tag(minecraft:emerald_ores): {}
minecraft:mossy_cobblestone: {}
- minecraft:diamond_ore: {}
- minecraft:deepslate_diamond_ore: {}
- minecraft:emerald_ore: {}
- minecraft:deepslate_emerald_ore: {}
- minecraft:chest: {}
- minecraft:ender_chest: {}
- minecraft:trapped_chest: {}
- minecraft:anvil: {}
- minecraft:crafter: {}
- minecraft:crafting_table: {}
- minecraft:dispenser: {}
- minecraft:enchanting_table: {}
- minecraft:furnace: {}
+ tag(minecraft:anvil): {}
+ tag(minecraft:beehives): {}
+ tag(minecraft:beds): {}
+ tag(minecraft:cauldrons): {}
minecraft:blast_furnace: {}
+ minecraft:brewing_stand: {}
minecraft:cartography_table: {}
+ minecraft:composter: {}
+ minecraft:crafting_table: {}
+ minecraft:enchanting_table: {}
minecraft:fletching_table: {}
+ minecraft:furnace: {}
minecraft:grindstone: {}
- minecraft:composter: {}
minecraft:lectern: {}
minecraft:loom: {}
minecraft:smithing_table: {}
minecraft:smoker: {}
minecraft:stonecutter: {}
- minecraft:hopper: {}
minecraft:spawner: {}
- minecraft:shulker_box: {}
- minecraft:white_shulker_box: {}
- minecraft:orange_shulker_box: {}
- minecraft:magenta_shulker_box: {}
- minecraft:light_blue_shulker_box: {}
- minecraft:yellow_shulker_box: {}
- minecraft:lime_shulker_box: {}
- minecraft:pink_shulker_box: {}
- minecraft:gray_shulker_box: {}
- minecraft:light_gray_shulker_box: {}
- minecraft:cyan_shulker_box: {}
- minecraft:purple_shulker_box: {}
- minecraft:blue_shulker_box: {}
- minecraft:brown_shulker_box: {}
- minecraft:green_shulker_box: {}
- minecraft:red_shulker_box: {}
- minecraft:black_shulker_box: {}
- minecraft:bee_nest: {}
- minecraft:beehive: {}
+ tag(minecraft:shulker_boxes): {}
+ minecraft:barrel: {}
+ minecraft:chest: {}
+ minecraft:crafter: {}
+ minecraft:dispenser: {}
+ minecraft:dropper: {}
+ minecraft:ender_chest: {}
+ minecraft:hopper: {}
+ minecraft:trapped_chest: {}
randomBlocks:
section-stone:
minY: -5
@@ -300,53 +243,42 @@ proximity:
onlyCheckCenter: false
useBlockBelow: true
hiddenBlocks:
- minecraft:chest: {}
- minecraft:ender_chest: {}
- minecraft:trapped_chest: {}
- minecraft:anvil: {}
- minecraft:crafter: {}
- minecraft:crafting_table: {}
- minecraft:dispenser: {}
- minecraft:enchanting_table: {}
- minecraft:furnace: {}
+ minecraft:ancient_debris: {}
+ minecraft:lodestone: {}
+ minecraft:nether_gold_ore: {}
+ minecraft:respawn_anchor: {}
+ tag(minecraft:anvil): {}
+ tag(minecraft:beehives): {}
+ tag(minecraft:beds): {}
+ tag(minecraft:cauldrons): {}
minecraft:blast_furnace: {}
+ minecraft:brewing_stand: {}
minecraft:cartography_table: {}
+ minecraft:composter: {}
+ minecraft:crafting_table: {}
+ minecraft:enchanting_table: {}
minecraft:fletching_table: {}
+ minecraft:furnace: {}
minecraft:grindstone: {}
- minecraft:composter: {}
minecraft:lectern: {}
minecraft:loom: {}
minecraft:smithing_table: {}
minecraft:smoker: {}
minecraft:stonecutter: {}
- minecraft:hopper: {}
minecraft:spawner: {}
- minecraft:shulker_box: {}
- minecraft:white_shulker_box: {}
- minecraft:orange_shulker_box: {}
- minecraft:magenta_shulker_box: {}
- minecraft:light_blue_shulker_box: {}
- minecraft:yellow_shulker_box: {}
- minecraft:lime_shulker_box: {}
- minecraft:pink_shulker_box: {}
- minecraft:gray_shulker_box: {}
- minecraft:light_gray_shulker_box: {}
- minecraft:cyan_shulker_box: {}
- minecraft:purple_shulker_box: {}
- minecraft:blue_shulker_box: {}
- minecraft:brown_shulker_box: {}
- minecraft:green_shulker_box: {}
- minecraft:red_shulker_box: {}
- minecraft:black_shulker_box: {}
- minecraft:bee_nest: {}
- minecraft:beehive: {}
- minecraft:ancient_debris: {}
- minecraft:nether_gold_ore: {}
- minecraft:respawn_anchor: {}
+ tag(minecraft:shulker_boxes): {}
+ minecraft:barrel: {}
+ minecraft:chest: {}
+ minecraft:crafter: {}
+ minecraft:dispenser: {}
+ minecraft:dropper: {}
+ minecraft:ender_chest: {}
+ minecraft:hopper: {}
+ minecraft:trapped_chest: {}
randomBlocks:
section-global:
minY: -2032
- maxY: 2032
+ maxY: 2031
blocks:
minecraft:netherrack: 1
proximity-end:
@@ -365,49 +297,37 @@ proximity:
onlyCheckCenter: false
useBlockBelow: true
hiddenBlocks:
- minecraft:chest: {}
- minecraft:ender_chest: {}
- minecraft:trapped_chest: {}
- minecraft:anvil: {}
- minecraft:crafter: {}
- minecraft:crafting_table: {}
- minecraft:dispenser: {}
- minecraft:enchanting_table: {}
- minecraft:furnace: {}
+ tag(minecraft:anvil): {}
+ tag(minecraft:beehives): {}
+ tag(minecraft:beds): {}
+ tag(minecraft:cauldrons): {}
minecraft:blast_furnace: {}
+ minecraft:brewing_stand: {}
minecraft:cartography_table: {}
+ minecraft:composter: {}
+ minecraft:crafting_table: {}
+ minecraft:enchanting_table: {}
minecraft:fletching_table: {}
+ minecraft:furnace: {}
minecraft:grindstone: {}
- minecraft:composter: {}
minecraft:lectern: {}
minecraft:loom: {}
minecraft:smithing_table: {}
minecraft:smoker: {}
minecraft:stonecutter: {}
- minecraft:hopper: {}
minecraft:spawner: {}
- minecraft:shulker_box: {}
- minecraft:white_shulker_box: {}
- minecraft:orange_shulker_box: {}
- minecraft:magenta_shulker_box: {}
- minecraft:light_blue_shulker_box: {}
- minecraft:yellow_shulker_box: {}
- minecraft:lime_shulker_box: {}
- minecraft:pink_shulker_box: {}
- minecraft:gray_shulker_box: {}
- minecraft:light_gray_shulker_box: {}
- minecraft:cyan_shulker_box: {}
- minecraft:purple_shulker_box: {}
- minecraft:blue_shulker_box: {}
- minecraft:brown_shulker_box: {}
- minecraft:green_shulker_box: {}
- minecraft:red_shulker_box: {}
- minecraft:black_shulker_box: {}
- minecraft:bee_nest: {}
- minecraft:beehive: {}
+ tag(minecraft:shulker_boxes): {}
+ minecraft:barrel: {}
+ minecraft:chest: {}
+ minecraft:crafter: {}
+ minecraft:dispenser: {}
+ minecraft:dropper: {}
+ minecraft:ender_chest: {}
+ minecraft:hopper: {}
+ minecraft:trapped_chest: {}
randomBlocks:
section-global:
minY: -2032
- maxY: 2032
+ maxY: 2031
blocks:
minecraft:end_stone: 1
diff --git a/orebfuscator-plugin/src/main/resources/config/config-1.18.yml b/orebfuscator-core/src/main/resources/config/config-1.21.9.yml
similarity index 58%
rename from orebfuscator-plugin/src/main/resources/config/config-1.18.yml
rename to orebfuscator-core/src/main/resources/config/config-1.21.9.yml
index 02455a163..de013be60 100644
--- a/orebfuscator-plugin/src/main/resources/config/config-1.18.yml
+++ b/orebfuscator-core/src/main/resources/config/config-1.21.9.yml
@@ -1,4 +1,4 @@
-version: 4
+version: 5
general:
checkForUpdates: true
updateOnBlockDamage: true
@@ -36,48 +36,29 @@ obfuscation:
- world
layerObfuscation: false
hiddenBlocks:
- - minecraft:emerald_ore
- - minecraft:deepslate_emerald_ore
- - minecraft:diamond_ore
- - minecraft:deepslate_diamond_ore
- - minecraft:gold_ore
- - minecraft:deepslate_gold_ore
+ - tag(minecraft:base_stone_overworld)
+ - tag(minecraft:coal_ores)
+ - tag(minecraft:copper_ores)
+ - tag(minecraft:diamond_ores)
+ - tag(minecraft:emerald_ores)
+ - tag(minecraft:gold_ores)
+ - tag(minecraft:iron_ores)
+ - tag(minecraft:lapis_ores)
+ - tag(minecraft:redstone_ores)
+ - minecraft:bedrock
+ - minecraft:raw_copper_block
- minecraft:raw_gold_block
- - minecraft:iron_ore
- - minecraft:deepslate_iron_ore
- minecraft:raw_iron_block
- - minecraft:coal_ore
- - minecraft:deepslate_coal_ore
- - minecraft:lapis_ore
- - minecraft:deepslate_lapis_ore
- - minecraft:redstone_ore
- - minecraft:deepslate_redstone_ore
- - minecraft:copper_ore
- - minecraft:raw_copper_block
- - minecraft:deepslate_copper_ore
- - minecraft:stone
- - minecraft:deepslate
+ - tag(minecraft:copper_chests)
+ - tag(minecraft:shulker_boxes)
+ - minecraft:barrel
- minecraft:chest
+ - minecraft:crafter
+ - minecraft:dispenser
+ - minecraft:dropper
- minecraft:ender_chest
+ - minecraft:hopper
- minecraft:trapped_chest
- - minecraft:shulker_box
- - minecraft:white_shulker_box
- - minecraft:orange_shulker_box
- - minecraft:magenta_shulker_box
- - minecraft:light_blue_shulker_box
- - minecraft:yellow_shulker_box
- - minecraft:lime_shulker_box
- - minecraft:pink_shulker_box
- - minecraft:gray_shulker_box
- - minecraft:light_gray_shulker_box
- - minecraft:cyan_shulker_box
- - minecraft:purple_shulker_box
- - minecraft:blue_shulker_box
- - minecraft:brown_shulker_box
- - minecraft:green_shulker_box
- - minecraft:red_shulker_box
- - minecraft:black_shulker_box
- - minecraft:bedrock
randomBlocks:
section-global:
minY: -2032
@@ -126,38 +107,26 @@ obfuscation:
- world_nether
layerObfuscation: false
hiddenBlocks:
- - minecraft:netherrack
+ - tag(minecraft:base_stone_nether)
+ - tag(minecraft:soul_fire_base_blocks)
+ - minecraft:ancient_debris
+ - minecraft:bedrock
+ - minecraft:nether_gold_ore
- minecraft:nether_quartz_ore
+ - tag(minecraft:copper_chests)
+ - tag(minecraft:shulker_boxes)
+ - minecraft:barrel
- minecraft:chest
+ - minecraft:crafter
+ - minecraft:dispenser
+ - minecraft:dropper
- minecraft:ender_chest
+ - minecraft:hopper
- minecraft:trapped_chest
- - minecraft:shulker_box
- - minecraft:white_shulker_box
- - minecraft:orange_shulker_box
- - minecraft:magenta_shulker_box
- - minecraft:light_blue_shulker_box
- - minecraft:yellow_shulker_box
- - minecraft:lime_shulker_box
- - minecraft:pink_shulker_box
- - minecraft:gray_shulker_box
- - minecraft:light_gray_shulker_box
- - minecraft:cyan_shulker_box
- - minecraft:purple_shulker_box
- - minecraft:blue_shulker_box
- - minecraft:brown_shulker_box
- - minecraft:green_shulker_box
- - minecraft:red_shulker_box
- - minecraft:black_shulker_box
- - minecraft:ancient_debris
- - minecraft:nether_gold_ore
- - minecraft:basalt
- - minecraft:soul_sand
- - minecraft:soul_soil
- - minecraft:bedrock
randomBlocks:
section-global:
minY: -2032
- maxY: 2032
+ maxY: 2031
blocks:
minecraft:netherrack: 4
minecraft:nether_quartz_ore: 1
@@ -181,30 +150,21 @@ obfuscation:
- world_the_end
layerObfuscation: false
hiddenBlocks:
+ - minecraft:end_stone
+ - tag(minecraft:copper_chests)
+ - tag(minecraft:shulker_boxes)
+ - minecraft:barrel
- minecraft:chest
+ - minecraft:crafter
+ - minecraft:dispenser
+ - minecraft:dropper
- minecraft:ender_chest
+ - minecraft:hopper
- minecraft:trapped_chest
- - minecraft:shulker_box
- - minecraft:white_shulker_box
- - minecraft:orange_shulker_box
- - minecraft:magenta_shulker_box
- - minecraft:light_blue_shulker_box
- - minecraft:yellow_shulker_box
- - minecraft:lime_shulker_box
- - minecraft:pink_shulker_box
- - minecraft:gray_shulker_box
- - minecraft:light_gray_shulker_box
- - minecraft:cyan_shulker_box
- - minecraft:purple_shulker_box
- - minecraft:blue_shulker_box
- - minecraft:brown_shulker_box
- - minecraft:green_shulker_box
- - minecraft:red_shulker_box
- - minecraft:black_shulker_box
randomBlocks:
section-global:
minY: -2032
- maxY: 2032
+ maxY: 2031
blocks:
minecraft:end_stone: 1
minecraft:end_stone_bricks: 1
@@ -228,50 +188,38 @@ proximity:
onlyCheckCenter: false
useBlockBelow: true
hiddenBlocks:
+ tag(minecraft:diamond_ores): {}
+ tag(minecraft:emerald_ores): {}
minecraft:mossy_cobblestone: {}
- minecraft:diamond_ore: {}
- minecraft:deepslate_diamond_ore: {}
- minecraft:emerald_ore: {}
- minecraft:deepslate_emerald_ore: {}
- minecraft:chest: {}
- minecraft:ender_chest: {}
- minecraft:trapped_chest: {}
- minecraft:anvil: {}
- minecraft:crafting_table: {}
- minecraft:dispenser: {}
- minecraft:enchanting_table: {}
- minecraft:furnace: {}
+ tag(minecraft:anvil): {}
+ tag(minecraft:beehives): {}
+ tag(minecraft:beds): {}
+ tag(minecraft:cauldrons): {}
minecraft:blast_furnace: {}
+ minecraft:brewing_stand: {}
minecraft:cartography_table: {}
+ minecraft:composter: {}
+ minecraft:crafting_table: {}
+ minecraft:enchanting_table: {}
minecraft:fletching_table: {}
+ minecraft:furnace: {}
minecraft:grindstone: {}
- minecraft:composter: {}
minecraft:lectern: {}
minecraft:loom: {}
minecraft:smithing_table: {}
minecraft:smoker: {}
minecraft:stonecutter: {}
- minecraft:hopper: {}
minecraft:spawner: {}
- minecraft:shulker_box: {}
- minecraft:white_shulker_box: {}
- minecraft:orange_shulker_box: {}
- minecraft:magenta_shulker_box: {}
- minecraft:light_blue_shulker_box: {}
- minecraft:yellow_shulker_box: {}
- minecraft:lime_shulker_box: {}
- minecraft:pink_shulker_box: {}
- minecraft:gray_shulker_box: {}
- minecraft:light_gray_shulker_box: {}
- minecraft:cyan_shulker_box: {}
- minecraft:purple_shulker_box: {}
- minecraft:blue_shulker_box: {}
- minecraft:brown_shulker_box: {}
- minecraft:green_shulker_box: {}
- minecraft:red_shulker_box: {}
- minecraft:black_shulker_box: {}
- minecraft:bee_nest: {}
- minecraft:beehive: {}
+ tag(minecraft:copper_chests): {}
+ tag(minecraft:shulker_boxes): {}
+ minecraft:barrel: {}
+ minecraft:chest: {}
+ minecraft:crafter: {}
+ minecraft:dispenser: {}
+ minecraft:dropper: {}
+ minecraft:ender_chest: {}
+ minecraft:hopper: {}
+ minecraft:trapped_chest: {}
randomBlocks:
section-stone:
minY: -5
@@ -299,52 +247,43 @@ proximity:
onlyCheckCenter: false
useBlockBelow: true
hiddenBlocks:
- minecraft:chest: {}
- minecraft:ender_chest: {}
- minecraft:trapped_chest: {}
- minecraft:anvil: {}
- minecraft:crafting_table: {}
- minecraft:dispenser: {}
- minecraft:enchanting_table: {}
- minecraft:furnace: {}
+ minecraft:ancient_debris: {}
+ minecraft:lodestone: {}
+ minecraft:nether_gold_ore: {}
+ minecraft:respawn_anchor: {}
+ tag(minecraft:anvil): {}
+ tag(minecraft:beehives): {}
+ tag(minecraft:beds): {}
+ tag(minecraft:cauldrons): {}
minecraft:blast_furnace: {}
+ minecraft:brewing_stand: {}
minecraft:cartography_table: {}
+ minecraft:composter: {}
+ minecraft:crafting_table: {}
+ minecraft:enchanting_table: {}
minecraft:fletching_table: {}
+ minecraft:furnace: {}
minecraft:grindstone: {}
- minecraft:composter: {}
minecraft:lectern: {}
minecraft:loom: {}
minecraft:smithing_table: {}
minecraft:smoker: {}
minecraft:stonecutter: {}
- minecraft:hopper: {}
minecraft:spawner: {}
- minecraft:shulker_box: {}
- minecraft:white_shulker_box: {}
- minecraft:orange_shulker_box: {}
- minecraft:magenta_shulker_box: {}
- minecraft:light_blue_shulker_box: {}
- minecraft:yellow_shulker_box: {}
- minecraft:lime_shulker_box: {}
- minecraft:pink_shulker_box: {}
- minecraft:gray_shulker_box: {}
- minecraft:light_gray_shulker_box: {}
- minecraft:cyan_shulker_box: {}
- minecraft:purple_shulker_box: {}
- minecraft:blue_shulker_box: {}
- minecraft:brown_shulker_box: {}
- minecraft:green_shulker_box: {}
- minecraft:red_shulker_box: {}
- minecraft:black_shulker_box: {}
- minecraft:bee_nest: {}
- minecraft:beehive: {}
- minecraft:ancient_debris: {}
- minecraft:nether_gold_ore: {}
- minecraft:respawn_anchor: {}
+ tag(minecraft:copper_chests): {}
+ tag(minecraft:shulker_boxes): {}
+ minecraft:barrel: {}
+ minecraft:chest: {}
+ minecraft:crafter: {}
+ minecraft:dispenser: {}
+ minecraft:dropper: {}
+ minecraft:ender_chest: {}
+ minecraft:hopper: {}
+ minecraft:trapped_chest: {}
randomBlocks:
section-global:
minY: -2032
- maxY: 2032
+ maxY: 2031
blocks:
minecraft:netherrack: 1
proximity-end:
@@ -363,48 +302,38 @@ proximity:
onlyCheckCenter: false
useBlockBelow: true
hiddenBlocks:
- minecraft:chest: {}
- minecraft:ender_chest: {}
- minecraft:trapped_chest: {}
- minecraft:anvil: {}
- minecraft:crafting_table: {}
- minecraft:dispenser: {}
- minecraft:enchanting_table: {}
- minecraft:furnace: {}
+ tag(minecraft:anvil): {}
+ tag(minecraft:beehives): {}
+ tag(minecraft:beds): {}
+ tag(minecraft:cauldrons): {}
minecraft:blast_furnace: {}
+ minecraft:brewing_stand: {}
minecraft:cartography_table: {}
+ minecraft:composter: {}
+ minecraft:crafting_table: {}
+ minecraft:enchanting_table: {}
minecraft:fletching_table: {}
+ minecraft:furnace: {}
minecraft:grindstone: {}
- minecraft:composter: {}
minecraft:lectern: {}
minecraft:loom: {}
minecraft:smithing_table: {}
minecraft:smoker: {}
minecraft:stonecutter: {}
- minecraft:hopper: {}
minecraft:spawner: {}
- minecraft:shulker_box: {}
- minecraft:white_shulker_box: {}
- minecraft:orange_shulker_box: {}
- minecraft:magenta_shulker_box: {}
- minecraft:light_blue_shulker_box: {}
- minecraft:yellow_shulker_box: {}
- minecraft:lime_shulker_box: {}
- minecraft:pink_shulker_box: {}
- minecraft:gray_shulker_box: {}
- minecraft:light_gray_shulker_box: {}
- minecraft:cyan_shulker_box: {}
- minecraft:purple_shulker_box: {}
- minecraft:blue_shulker_box: {}
- minecraft:brown_shulker_box: {}
- minecraft:green_shulker_box: {}
- minecraft:red_shulker_box: {}
- minecraft:black_shulker_box: {}
- minecraft:bee_nest: {}
- minecraft:beehive: {}
+ tag(minecraft:copper_chests): {}
+ tag(minecraft:shulker_boxes): {}
+ minecraft:barrel: {}
+ minecraft:chest: {}
+ minecraft:crafter: {}
+ minecraft:dispenser: {}
+ minecraft:dropper: {}
+ minecraft:ender_chest: {}
+ minecraft:hopper: {}
+ minecraft:trapped_chest: {}
randomBlocks:
section-global:
minY: -2032
- maxY: 2032
+ maxY: 2031
blocks:
minecraft:end_stone: 1
diff --git a/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/DummyException.java b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/DummyException.java
new file mode 100644
index 000000000..5fbfb7afc
--- /dev/null
+++ b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/DummyException.java
@@ -0,0 +1,27 @@
+package dev.imprex.orebfuscator;
+
+/**
+ * DummyException is a singleton exception used in tests to ensure a consistent stack trace. The stack trace is fixed
+ * and shouldn't be changed, even if the class is modified.
+ */
+public class DummyException extends Exception {
+
+ /**
+ * The single instance of DummyException used across all tests.
+ */
+ public static final DummyException INSTANCE = new DummyException();
+
+ /**
+ * Private constructor to prevent additional instances. The stack trace is fixed and the message is set to "dummy
+ * exception" to maintain consistency in tests.
+ */
+ private DummyException() {
+ super("dummy exception");
+
+ // Set a fixed stack trace
+ this.setStackTrace(new StackTraceElement[]{
+ new StackTraceElement("dev.imprex.orebfuscator.DummyException", "", "DummyException.java", 26),
+ new StackTraceElement("dev.imprex.orebfuscator.DummyException", "", "DummyException.java", 18)
+ });
+ }
+}
diff --git a/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/config/ProximityHeightConditionTest.java b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/config/ProximityHeightConditionTest.java
new file mode 100644
index 000000000..25012d37b
--- /dev/null
+++ b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/config/ProximityHeightConditionTest.java
@@ -0,0 +1,51 @@
+package dev.imprex.orebfuscator.config;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+public class ProximityHeightConditionTest {
+
+ private static final int TEST_MIN = ProximityHeightCondition.clampY(-0xFFF);
+ private static final int TEST_MAX = ProximityHeightCondition.clampY(0xFFF);
+
+ @Test
+ public void testCreateRemove() {
+ final int minY = -10;
+ final int maxY = 10;
+
+ int flag = 0b101;
+ assertFalse(ProximityHeightCondition.isPresent(flag));
+
+ flag |= ProximityHeightCondition.create(minY, maxY);
+ assertTrue(ProximityHeightCondition.isPresent(flag));
+ assertEquals(minY, ProximityHeightCondition.getMinY(flag));
+ assertEquals(maxY, ProximityHeightCondition.getMaxY(flag));
+
+ for (int y = TEST_MIN; y <= TEST_MAX; y++) {
+ boolean expected = minY <= y && maxY >= y;
+ assertEquals(expected, ProximityHeightCondition.match(flag, y), "failed for " + y);
+ }
+
+ int other = ProximityHeightCondition.create(minY, maxY);
+ assertTrue(ProximityHeightCondition.equals(flag, other));
+
+ flag = ProximityHeightCondition.remove(flag);
+ assertEquals(0b101, flag);
+ }
+
+ @Test
+ public void testReadWrite() {
+ for (int minY = TEST_MIN; minY <= TEST_MAX; minY++) {
+ for (int maxY = minY; maxY <= TEST_MAX; maxY++) {
+ int flag = ProximityHeightCondition.create(minY, maxY);
+
+ assertTrue(ProximityHeightCondition.isPresent(flag));
+ assertEquals(minY, ProximityHeightCondition.getMinY(flag));
+ assertEquals(maxY, ProximityHeightCondition.getMaxY(flag));
+ }
+ }
+ }
+}
diff --git a/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/config/context/ConfigMessageTest.java b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/config/context/ConfigMessageTest.java
new file mode 100644
index 000000000..132580796
--- /dev/null
+++ b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/config/context/ConfigMessageTest.java
@@ -0,0 +1,20 @@
+package dev.imprex.orebfuscator.config.context;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+class ConfigMessageTest {
+
+ @Test
+ void testFixedMessageFormat() {
+ String formatted = ConfigMessage.MISSING_OR_EMPTY.format("ignored");
+ assertEquals("is missing or empty", formatted);
+ }
+
+ @Test
+ void testDynamicMessageFormat() {
+ String formatted = ConfigMessage.VALUE_MIN.format(10, 5);
+ assertEquals("value too low {value(10) < min(5)}", formatted);
+ }
+}
diff --git a/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/config/context/DefaultConfigParsingContextTest.java b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/config/context/DefaultConfigParsingContextTest.java
new file mode 100644
index 000000000..b02cfe9c3
--- /dev/null
+++ b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/config/context/DefaultConfigParsingContextTest.java
@@ -0,0 +1,159 @@
+package dev.imprex.orebfuscator.config.context;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+import org.junit.jupiter.api.Test;
+
+class DefaultConfigParsingContextTest {
+
+ @Test
+ void testNoErrorsNoWarnings() {
+ DefaultConfigParsingContext context = new DefaultConfigParsingContext();
+
+ assertFalse(context.hasErrors());
+ assertNull(context.report());
+ }
+
+ @Test
+ void testWarnDoesNotSetHasErrors() {
+ DefaultConfigParsingContext context = new DefaultConfigParsingContext();
+ context.warn(ConfigMessage.MISSING_OR_EMPTY);
+
+ assertFalse(context.hasErrors());
+ assertNotNull(context.report());
+ }
+
+ @Test
+ void testErrorSetsHasErrors() {
+ DefaultConfigParsingContext context = new DefaultConfigParsingContext();
+ context.error(ConfigMessage.MISSING_OR_EMPTY);
+
+ assertTrue(context.hasErrors());
+ assertNotNull(context.report());
+ }
+
+ @Test
+ void testErrorMinValue() {
+ // value below min
+ DefaultConfigParsingContext context = new DefaultConfigParsingContext();
+ context.errorMinValue("value", 10, 5);
+ assertTrue(context.hasErrors());
+
+ // value equals min
+ DefaultConfigParsingContext context2 = new DefaultConfigParsingContext();
+ context2.errorMinValue("value", 10, 10);
+ assertFalse(context2.hasErrors());
+
+ // value above min
+ DefaultConfigParsingContext context3 = new DefaultConfigParsingContext();
+ context3.errorMinValue("value", 10, 15);
+ assertFalse(context3.hasErrors());
+ }
+
+ @Test
+ void testErrorMinMaxValue() {
+ // value below allowed range
+ DefaultConfigParsingContext context = new DefaultConfigParsingContext();
+ context.errorMinMaxValue("value", 5, 10, 3);
+ assertTrue(context.hasErrors());
+
+ // value within range (min-bound)
+ DefaultConfigParsingContext context2 = new DefaultConfigParsingContext();
+ context2.errorMinMaxValue("value", 5, 10, 5);
+ assertFalse(context2.hasErrors());
+
+ // value within range
+ DefaultConfigParsingContext context3 = new DefaultConfigParsingContext();
+ context3.errorMinMaxValue("value", 5, 10, 7);
+ assertFalse(context3.hasErrors());
+
+ // value within range (max-bound)
+ DefaultConfigParsingContext context4 = new DefaultConfigParsingContext();
+ context4.errorMinMaxValue("value", 5, 10, 10);
+ assertFalse(context4.hasErrors());
+
+ // value above allowed range
+ DefaultConfigParsingContext context5 = new DefaultConfigParsingContext();
+ context5.errorMinMaxValue("value", 5, 10, 15);
+ assertTrue(context5.hasErrors());
+ }
+
+ @Test
+ void testNestedChildContextNonIsolated() {
+ DefaultConfigParsingContext context = new DefaultConfigParsingContext();
+ context.section("child").error(ConfigMessage.MISSING_OR_EMPTY);
+ assertTrue(context.hasErrors());
+
+ DefaultConfigParsingContext context2 = new DefaultConfigParsingContext();
+ context2.error("child", ConfigMessage.MISSING_OR_EMPTY);
+ assertTrue(context2.hasErrors());
+ }
+
+ @Test
+ void testNestedChildContextIsolated() {
+ DefaultConfigParsingContext context = new DefaultConfigParsingContext();
+ ConfigParsingContext child = context.section("child", true);
+
+ child.error(ConfigMessage.MISSING_OR_EMPTY);
+ context.error("child.b", ConfigMessage.MISSING_OR_EMPTY);
+
+ assertFalse(context.hasErrors());
+ assertTrue(child.hasErrors());
+ }
+
+ @Test
+ void testSectionPathValidation() {
+ DefaultConfigParsingContext context = new DefaultConfigParsingContext();
+ // Paths with blank (non-empty) segments (e.g. "\t" or "a. .b") should throw an exception.
+ assertThrows(IllegalArgumentException.class, () -> context.section("\t"));
+ assertThrows(IllegalArgumentException.class, () -> context.section("a. .b"));
+ }
+
+ @Test
+ void testDisableIfError() {
+ DefaultConfigParsingContext context = new DefaultConfigParsingContext();
+ assertTrue(context.disableIfError(true));
+ assertFalse(context.disableIfError(false));
+
+ context.error("child", ConfigMessage.MISSING_OR_EMPTY);
+ assertFalse(context.disableIfError(true));
+ assertFalse(context.disableIfError(false));
+
+ // test if error isolation also works for disableIfError
+ ConfigParsingContext child = context.section("child", true);
+ assertTrue(context.disableIfError(true));
+ assertFalse(child.disableIfError(true));
+ }
+
+ @Test
+ void testReportWithNestedMessages() throws IOException {
+ DefaultConfigParsingContext context = new DefaultConfigParsingContext();
+ ConfigParsingContext child = context.section("child", true);
+
+ // test all variations of warn
+ context.warn(ConfigMessage.MISSING_OR_EMPTY);
+ context.warn("a.b.c", ConfigMessage.MISSING_OR_EMPTY);
+ child.warn(ConfigMessage.MISSING_OR_EMPTY);
+ context.warn("child.a", ConfigMessage.MISSING_OR_EMPTY);
+
+ // test all variations of error
+ context.error(ConfigMessage.MISSING_OR_EMPTY);
+ context.error("a.b.c", ConfigMessage.MISSING_OR_EMPTY);
+ child.error(ConfigMessage.MISSING_OR_EMPTY);
+ context.error("child.a", ConfigMessage.MISSING_OR_EMPTY);
+
+ assertFalse(child.disableIfError(true));
+
+ String expected = Files.readString(Paths.get("src/test/resources/config/context-test-report.txt"));
+ assertEquals(expected, context.report());
+ }
+}
diff --git a/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/config/yaml/ConfigurationSectionTest.java b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/config/yaml/ConfigurationSectionTest.java
new file mode 100644
index 000000000..9267e220b
--- /dev/null
+++ b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/config/yaml/ConfigurationSectionTest.java
@@ -0,0 +1,471 @@
+package dev.imprex.orebfuscator.config.yaml;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+class ConfigurationSectionTest {
+
+ @Test
+ void testGetName() {
+ ConfigurationSection section = new ConfigurationSection("");
+ assertEquals("", section.getName());
+
+ ConfigurationSection child = section.createSection("child");
+ assertEquals("child", child.getName());
+ }
+
+ @Test
+ void testGetKeys() {
+ ConfigurationSection section = new ConfigurationSection("");
+
+ section.set("a", 0);
+ section.set("b.a", 0);
+ section.set("b.b", 0);
+ section.set("b.b.a", 0);
+
+ assertArrayEquals(new String[]{"a", "b"}, section.getKeys().toArray());
+ }
+
+ @Test
+ void testContains() {
+ ConfigurationSection section = new ConfigurationSection("");
+
+ section.set("a", 0);
+ section.set("b.c", 0);
+ section.set("c", null);
+
+ assertTrue(section.contains("a"));
+ assertTrue(section.contains("b"));
+ assertFalse(section.contains("c"));
+
+ assertFalse(section.contains("b.a"));
+ assertFalse(section.contains("b.b"));
+ assertTrue(section.contains("b.c"));
+ }
+
+ @Test
+ void testSet() {
+ ConfigurationSection section = new ConfigurationSection("");
+
+ section.set("boolean", true);
+
+ assertTrue(section.contains("boolean"));
+ assertEquals(true, section.get("boolean"));
+
+ section.set("boolean", null);
+
+ assertFalse(section.contains("boolean"));
+ assertNull(section.get("boolean"));
+ }
+
+ @Test
+ void testSetNullDoesNotCreateSubSection() {
+ ConfigurationSection section = new ConfigurationSection("");
+
+ section.set("int.32", null);
+
+ assertFalse(section.contains("int"));
+ assertNull(section.get("int"));
+ }
+
+ @Test
+ void testGet() {
+ ConfigurationSection section = new ConfigurationSection("");
+
+ section.set("boolean", true);
+ section.set("int.32", Integer.MAX_VALUE);
+ section.set("int.64", Long.MAX_VALUE);
+
+ assertEquals(section, section.get(""));
+ assertEquals(true, section.get("boolean"));
+ assertEquals(Integer.MAX_VALUE, section.get("int.32"));
+ assertEquals(Long.MAX_VALUE, section.get("int.64"));
+ }
+
+ @Test
+ void testGetWithDefaultValue() {
+ ConfigurationSection section = new ConfigurationSection("");
+
+ section.set("boolean", true);
+ assertEquals(true, section.get("boolean"));
+ assertEquals(true, section.get("boolean", false));
+
+ assertNull(section.get("string"));
+ assertEquals("foo", section.get("string", "foo"));
+
+ assertNull(section.get("int.32"));
+ assertEquals(Integer.MAX_VALUE, section.get("int.32", Integer.MAX_VALUE));
+ }
+
+ @Test
+ void testCreateSection() {
+ ConfigurationSection section = new ConfigurationSection("");
+
+ assertEquals(section, section.createSection(""));
+ assertEquals(section.createSection("should.be.same.instance"), section.createSection("should.be.same.instance"));
+
+ assertNotNull(section.createSection("int"));
+ assertNotNull(section.createSection("foo.bar"));
+ assertNotNull(section.createSection("foo.baz"));
+ }
+
+ @Test
+ void testIsBoolean() {
+ ConfigurationSection section = new ConfigurationSection("");
+
+ section.set("boolean", true);
+ section.set("booleanObject", Boolean.TRUE);
+ section.set("other", 1);
+
+ assertTrue(section.isBoolean("boolean"));
+ assertTrue(section.isBoolean("booleanObject"));
+ assertFalse(section.isBoolean("other"));
+ assertFalse(section.isBoolean("unknown"));
+ }
+
+ @Test
+ void testGetBoolean() {
+ ConfigurationSection section = new ConfigurationSection("");
+
+ section.set("boolean", true);
+ section.set("booleanObject", Boolean.TRUE);
+ section.set("other", 1);
+
+ assertEquals(true, section.getBoolean("boolean"));
+ assertEquals(true, section.getBoolean("booleanObject"));
+ assertNull(section.getBoolean("other"));
+ assertNull(section.getBoolean("unknown"));
+ }
+
+ @Test
+ void testGetBooleanWithDefaultValue() {
+ ConfigurationSection section = new ConfigurationSection("");
+
+ section.set("boolean", true);
+ section.set("booleanObject", Boolean.TRUE);
+ section.set("other", 1);
+
+ assertEquals(true, section.getBoolean("boolean", false));
+ assertEquals(true, section.getBoolean("booleanObject", false));
+ assertEquals(false, section.getBoolean("other", false));
+ assertEquals(false, section.getBoolean("unknown", false));
+ }
+
+ @Test
+ void testIsInt() {
+ ConfigurationSection section = new ConfigurationSection("");
+
+ section.set("int", 1);
+ section.set("intObject", Integer.valueOf(1));
+ section.set("number", 1f);
+ section.set("other", true);
+
+ assertTrue(section.isInt("int"));
+ assertTrue(section.isInt("intObject"));
+ assertFalse(section.isInt("number"));
+ assertFalse(section.isInt("other"));
+ assertFalse(section.isInt("unknown"));
+ }
+
+ @Test
+ void testGetInt() {
+ ConfigurationSection section = new ConfigurationSection("");
+
+ section.set("int", 1);
+ section.set("intObject", Integer.valueOf(1));
+ section.set("number", 1f);
+ section.set("other", true);
+
+ assertEquals(1, section.getInt("int"));
+ assertEquals(1, section.getInt("intObject"));
+ assertEquals(1, section.getInt("number"));
+ assertNull(section.getInt("other"));
+ assertNull(section.getInt("unknown"));
+ }
+
+ @Test
+ void testGetIntWithDefaultValue() {
+ ConfigurationSection section = new ConfigurationSection("");
+
+ section.set("int", 1);
+ section.set("intObject", Integer.valueOf(1));
+ section.set("number", 1f);
+ section.set("other", true);
+
+ assertEquals(1, section.getInt("int", -1));
+ assertEquals(1, section.getInt("intObject", -1));
+ assertEquals(1, section.getInt("number", -1));
+ assertEquals(-1, section.getInt("other", -1));
+ assertEquals(-1, section.getInt("unknown", -1));
+ }
+
+ @Test
+ void testIsLong() {
+ ConfigurationSection section = new ConfigurationSection("");
+
+ section.set("long", 1L);
+ section.set("longObject", Long.valueOf(1L));
+ section.set("number", 1);
+ section.set("other", true);
+
+ assertTrue(section.isLong("long"));
+ assertTrue(section.isLong("longObject"));
+ assertFalse(section.isLong("number"));
+ assertFalse(section.isLong("other"));
+ assertFalse(section.isLong("unknown"));
+ }
+
+ @Test
+ void testGetLong() {
+ ConfigurationSection section = new ConfigurationSection("");
+
+ section.set("long", 1L);
+ section.set("longObject", Long.valueOf(1L));
+ section.set("number", 1);
+ section.set("other", true);
+
+ assertEquals(1L, section.getLong("long"));
+ assertEquals(1L, section.getLong("longObject"));
+ assertEquals(1L, section.getLong("number"));
+ assertNull(section.getLong("other"));
+ assertNull(section.getLong("unknown"));
+ }
+
+ @Test
+ void testGetLongWithDefaultValue() {
+ ConfigurationSection section = new ConfigurationSection("");
+
+ section.set("long", 1L);
+ section.set("longObject", Long.valueOf(1L));
+ section.set("number", 1);
+ section.set("other", true);
+
+ assertEquals(1L, section.getLong("long", -1L));
+ assertEquals(1L, section.getLong("longObject", -1L));
+ assertEquals(1L, section.getLong("number", -1L));
+ assertEquals(-1L, section.getLong("other", -1L));
+ assertEquals(-1L, section.getLong("unknown", -1L));
+ }
+
+ @Test
+ void testIsDouble() {
+ ConfigurationSection section = new ConfigurationSection("");
+
+ section.set("double", 1d);
+ section.set("doubleObject", Double.valueOf(1d));
+ section.set("number", 1);
+ section.set("other", true);
+
+ assertTrue(section.isDouble("double"));
+ assertTrue(section.isDouble("doubleObject"));
+ assertFalse(section.isDouble("number"));
+ assertFalse(section.isDouble("other"));
+ assertFalse(section.isDouble("unknown"));
+ }
+
+ @Test
+ void testGetDouble() {
+ ConfigurationSection section = new ConfigurationSection("");
+
+ section.set("double", 1d);
+ section.set("doubleObject", Double.valueOf(1d));
+ section.set("number", 1);
+ section.set("other", true);
+
+ assertEquals(1d, section.getDouble("double"));
+ assertEquals(1d, section.getDouble("doubleObject"));
+ assertEquals(1d, section.getDouble("number"));
+ assertNull(section.getDouble("other"));
+ assertNull(section.getDouble("unknown"));
+ }
+
+ @Test
+ void testGetDoubleWithDefaultValue() {
+ ConfigurationSection section = new ConfigurationSection("");
+
+ section.set("double", 1d);
+ section.set("doubleObject", Double.valueOf(1d));
+ section.set("number", 1);
+ section.set("other", true);
+
+ assertEquals(1d, section.getDouble("double", -1d));
+ assertEquals(1d, section.getDouble("doubleObject", -1d));
+ assertEquals(1d, section.getDouble("number", -1d));
+ assertEquals(-1d, section.getDouble("other", -1d));
+ assertEquals(-1d, section.getDouble("unknown", -1d));
+ }
+
+ @Test
+ void testIsNumber() {
+ ConfigurationSection section = new ConfigurationSection("");
+
+ section.set("int8", (byte) 1);
+ section.set("int16", (short) 1);
+ section.set("int32", 1);
+ section.set("int64", 1L);
+ section.set("float32", 1f);
+ section.set("float64", 1d);
+ section.set("other", true);
+
+ assertTrue(section.isNumber("int8"));
+ assertTrue(section.isNumber("int16"));
+ assertTrue(section.isNumber("int32"));
+ assertTrue(section.isNumber("int64"));
+ assertTrue(section.isNumber("float32"));
+ assertTrue(section.isNumber("float64"));
+ assertFalse(section.isNumber("other"));
+ }
+
+ @Test
+ void testIsString() {
+ ConfigurationSection section = new ConfigurationSection("");
+
+ section.set("string", "foo");
+ section.set("number", 1);
+ section.set("other", true);
+
+ assertTrue(section.isString("string"));
+ assertFalse(section.isString("number"));
+ assertFalse(section.isString("other"));
+ assertFalse(section.isString("unknown"));
+ }
+
+ @Test
+ void testGetString() {
+ ConfigurationSection section = new ConfigurationSection("");
+
+ section.set("string", "foo");
+ section.set("number", 1);
+ section.set("other", true);
+
+ assertEquals("foo", section.getString("string"));
+ assertNull(section.getString("number"));
+ assertNull(section.getString("other"));
+ assertNull(section.getString("unknown"));
+ }
+
+ @Test
+ void testGetStringWithDefaultValue() {
+ ConfigurationSection section = new ConfigurationSection("");
+
+ section.set("string", "foo");
+ section.set("number", 1);
+ section.set("other", true);
+
+ assertEquals("foo", section.getString("string", "bar"));
+ assertEquals("bar", section.getString("number", "bar"));
+ assertEquals("bar", section.getString("other", "bar"));
+ assertEquals("bar", section.getString("unknown", "bar"));
+ }
+
+ @Test
+ void testIsList() {
+ ConfigurationSection section = new ConfigurationSection("");
+
+ section.set("list", List.of());
+ section.set("other", true);
+
+ assertTrue(section.isList("list"));
+ assertFalse(section.isList("other"));
+ assertFalse(section.isList("unknown"));
+ }
+
+ @Test
+ void testGetList() {
+ ConfigurationSection section = new ConfigurationSection("");
+
+ section.set("list", List.of());
+ section.set("other", true);
+
+ assertEquals(List.of(), section.getList("list"));
+ assertNull(section.getList("other"));
+ assertNull(section.getList("unknown"));
+ }
+
+ @Test
+ void testGetListWithDefaultValue() {
+ ConfigurationSection section = new ConfigurationSection("");
+
+ section.set("list", List.of());
+ section.set("other", true);
+
+ assertEquals(List.of(), section.getList("list", List.of(true)));
+ assertEquals(List.of(true), section.getList("other", List.of(true)));
+ assertEquals(List.of(true), section.getList("unknown", List.of(true)));
+ }
+
+ @Test
+ void testGetStringList() {
+ ConfigurationSection section = new ConfigurationSection("");
+
+ section.set("list", List.of(
+ true,
+ (byte) 1,
+ (short) 2,
+ 3,
+ 4L,
+ 5.5f,
+ 6.6d,
+ 'a',
+ "foo",
+ List.of()));
+ section.set("other", true);
+
+ assertEquals(List.of(
+ "true",
+ "1",
+ "2",
+ "3",
+ "4",
+ "5.5",
+ "6.6",
+ "a",
+ "foo"), section.getStringList("list"));
+ assertEquals(Collections.EMPTY_LIST, section.getStringList("other"));
+ assertEquals(Collections.EMPTY_LIST, section.getStringList("unknown"));
+ }
+
+ @Test
+ void testIsSection() {
+ ConfigurationSection section = new ConfigurationSection("");
+
+ section.createSection("section");
+ section.set("other", true);
+
+ assertTrue(section.isSection("section"));
+ assertFalse(section.isSection("other"));
+ assertFalse(section.isSection("unknown"));
+ }
+
+ @Test
+ void testGetSection() {
+ ConfigurationSection section = new ConfigurationSection("");
+
+ ConfigurationSection child = section.createSection("section");
+ section.set("other", true);
+
+ assertEquals(child, section.getSection("section"));
+ assertNull(section.getSection("other"));
+ assertNull(section.getSection("unknown"));
+ }
+
+ @Test
+ void testGetSubSection() {
+ ConfigurationSection section = new ConfigurationSection("");
+
+ ConfigurationSection sectionA = section.createSection("sectionA");
+ ConfigurationSection sectionB = section.createSection("sectionB");
+ section.set("other", true);
+
+ assertEquals(List.of(sectionA, sectionB), section.getSubSections());
+ }
+}
diff --git a/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/config/yaml/YamlConfigurationTest.java b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/config/yaml/YamlConfigurationTest.java
new file mode 100644
index 000000000..016494b17
--- /dev/null
+++ b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/config/yaml/YamlConfigurationTest.java
@@ -0,0 +1,104 @@
+package dev.imprex.orebfuscator.config.yaml;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+class YamlConfigurationTest {
+
+ private static final String SEQUENCE_COMMENT_EXAMPLE = "#header\nlist:\n- a # inline a\n- b # inline b\n- c #inline c\n#footer\n";
+
+ @Test
+ void testSaveEmpty() throws IOException {
+ assertEquals("", new YamlConfiguration().withoutComments());
+ }
+
+ @Test
+ void testInvalidConfig() throws IOException, InvalidConfigurationException {
+ try (InputStream inputStream = toInputStream("\tboolean: true")) {
+ assertThrows(InvalidConfigurationException.class, () -> YamlConfiguration.loadConfig(inputStream));
+ }
+
+ try (InputStream inputStream = toInputStream("- a\n- b\n")) {
+ assertThrows(InvalidConfigurationException.class, () -> YamlConfiguration.loadConfig(inputStream));
+ }
+
+ try (InputStream inputStream = toInputStream("key.with.path: 1")) {
+ assertThrows(InvalidConfigurationException.class, () -> YamlConfiguration.loadConfig(inputStream));
+ }
+
+ try (InputStream inputStream = toInputStream("root: \n '': 1")) {
+ assertThrows(InvalidConfigurationException.class, () -> YamlConfiguration.loadConfig(inputStream));
+ }
+
+ try (InputStream inputStream = toInputStream("root: \n '\t': 1")) {
+ assertThrows(InvalidConfigurationException.class, () -> YamlConfiguration.loadConfig(inputStream));
+ }
+ }
+
+ @Test
+ void testSerializationRoundTrip(@TempDir Path tempDir) throws IOException, InvalidConfigurationException {
+ YamlConfiguration original = new YamlConfiguration();
+
+ original.set("boolean", true);
+ original.set("child.int", -1);
+ original.set("child.double", -1.2d);
+ original.set("strings.foo", "foo");
+ original.set("strings.bar", "bar");
+ original.set("strings.baz.qux", "qux");
+ original.set("list", List.of("a", "b", "c", "d"));
+
+ Path filePath = tempDir.resolve("test-serialization-round-trip.yml");
+ original.save(filePath);
+
+ YamlConfiguration read = YamlConfiguration.loadConfig(filePath);
+ assertEquals(original, read);
+ }
+
+ @Test
+ void testCommentPreservation(@TempDir Path tempDir) throws IOException, InvalidConfigurationException {
+ Path expectedPath = Paths.get("src/test/resources/config/example-config.yml");
+ YamlConfiguration configuration = YamlConfiguration.loadConfig(expectedPath);
+
+ Path actualPath = tempDir.resolve("test-deserialization-round-trip.yml");
+ configuration.save(actualPath);
+
+ assertEquals(Files.readString(expectedPath), Files.readString(actualPath));
+ }
+
+ @Test
+ void testSequenceCommentPreservation(@TempDir Path tempDir) throws IOException, InvalidConfigurationException {
+ try (InputStream inputStream = toInputStream(SEQUENCE_COMMENT_EXAMPLE)) {
+ YamlConfiguration configuration = YamlConfiguration.loadConfig(inputStream);
+ configuration.set("list", List.of("a", "c"));
+
+ Path actualPath = tempDir.resolve("test-sequence-comment-preservation.yml");
+ configuration.save(actualPath);
+
+ assertEquals("#header\nlist:\n- a # inline a\n- c #inline c\n#footer\n", Files.readString(actualPath));
+ }
+ }
+
+ @Test
+ void testWithoutComments() throws IOException, InvalidConfigurationException {
+ try (InputStream inputStream = toInputStream(SEQUENCE_COMMENT_EXAMPLE)) {
+ YamlConfiguration configuration = YamlConfiguration.loadConfig(inputStream);
+ assertEquals("list:\n- a\n- b\n- c\n", configuration.withoutComments());
+ }
+ }
+
+ private static InputStream toInputStream(String value) {
+ return new ByteArrayInputStream(value.getBytes(StandardCharsets.UTF_8));
+ }
+}
diff --git a/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/reflect/ReflectorTest.java b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/reflect/ReflectorTest.java
new file mode 100644
index 000000000..0653d6644
--- /dev/null
+++ b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/reflect/ReflectorTest.java
@@ -0,0 +1,85 @@
+package dev.imprex.orebfuscator.reflect;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.lang.reflect.Modifier;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.regex.Pattern;
+import org.junit.jupiter.api.Test;
+import dev.imprex.orebfuscator.reflect.dummy.Entity;
+import dev.imprex.orebfuscator.reflect.dummy.Player;
+
+class ReflectorTest {
+
+ private static final String REQUIREMENTS_FIELD = decode(
+ "ewogIHJlcXVpcmVkTW9kaWZpZXJzOiBwcml2YXRlIHN5bmNocm9uaXplZCwKICBiYW5uZWRNb2RpZmllcnM6IHN0YXRpYywKICBpbmNsdWRlU3ludGhldGljLAogIG5hbWU6IFxRdGVzdFxFLAogIGRlY2xhcmluZ0NsYXNzOiB7aXMgZGV2LmltcHJleC5vcmViZnVzY2F0b3IucmVmbGVjdC5kdW1teS5FbnRpdHl9LAogIHR5cGU6IHtpcyB2b2lkfQp9");
+ private static final String REQUIREMENTS_METHOD = decode(
+ "ewogIHJlcXVpcmVkTW9kaWZpZXJzOiBwcml2YXRlIHN5bmNocm9uaXplZCwKICBiYW5uZWRNb2RpZmllcnM6IHN0YXRpYywKICBpbmNsdWRlU3ludGhldGljLAogIG5hbWU6IFxRdGVzdFxFLAogIGRlY2xhcmluZ0NsYXNzOiB7aXMgZGV2LmltcHJleC5vcmViZnVzY2F0b3IucmVmbGVjdC5kdW1teS5FbnRpdHl9LAogIGV4Y2VwdGlvbkNsYXNzOiB7CiAgICA8YW55Pj17c3ViLWNsYXNzLW9mIGphdmEubGFuZy5SdW50aW1lRXhjZXB0aW9ufSwKICAgIDA9e2lzIGphdmEubGFuZy5OdWxsUG9pbnRlckV4Y2VwdGlvbn0KICB9LAogIHBhcmFtZXRlckNsYXNzOiB7CiAgICA8YW55Pj17aXMgaW50fSwKICAgIDA9e2lzIGludH0sCiAgICAyPXtzdXBlci1jbGFzcy1vZiBkZXYuaW1wcmV4Lm9yZWJmdXNjYXRvci5yZWZsZWN0LmR1bW15LkVudGl0eX0sCiAgICA0PXtyZWdleCAuKlR5cGV9CiAgfSwKICBwYXJhbWV0ZXJDb3VudDogNiwKICByZXR1cm5UeXBlOiB7aXMgdm9pZH0KfQ==");
+
+ private static String decode(String input) {
+ return new String(Base64.getDecoder().decode(input), StandardCharsets.UTF_8);
+ }
+
+ @Test
+ void testRecursive() {
+ Reflector reflector = Reflector.of(Player.class)
+ .recursive();
+
+ assertEquals(4, reflector.constructor()
+ .stream().count());
+ assertEquals(1, reflector.constructor()
+ .declaringClass().is(Object.class)
+ .stream().count());
+ }
+
+ @Test
+ void testRecursiveUntil() {
+ Reflector reflector = Reflector.of(Player.class)
+ .recursiveUntil(Entity.class);
+
+ assertEquals(3, reflector.constructor()
+ .stream().count());
+ assertEquals(0, reflector.constructor()
+ .declaringClass().is(Object.class)
+ .stream().count());
+ }
+
+ @Test
+ void testRequirementFormatting() {
+ Reflector reflector = Reflector.of(Entity.class);
+
+ var exception = assertThrows(IllegalArgumentException.class, () -> {
+ reflector.field()
+ .requireModifier(Modifier.PRIVATE | Modifier.SYNCHRONIZED)
+ .banStatic()
+ .declaringClass().is(Entity.class)
+ .nameIs("test")
+ .type().is(void.class)
+ .includeSynthetic()
+ .firstOrThrow();
+ });
+ assertTrue(exception.getMessage().endsWith(REQUIREMENTS_FIELD));
+
+ exception = assertThrows(IllegalArgumentException.class, () -> {
+ reflector.method()
+ .requireModifier(Modifier.PRIVATE | Modifier.SYNCHRONIZED)
+ .banStatic()
+ .declaringClass().is(Entity.class)
+ .nameIs("test")
+ .exception(0).is(NullPointerException.class)
+ .exception().subOf(RuntimeException.class)
+ .parameter().is(int.class)
+ .parameter(2).superOf(Entity.class)
+ .parameter(0).is(int.class)
+ .parameter(4).regex(Pattern.compile(".*Type", Pattern.DOTALL))
+ .returnType().is(void.class)
+ .includeSynthetic()
+ .parameterCount(6)
+ .firstOrThrow();
+ });
+ assertTrue(exception.getMessage().endsWith(REQUIREMENTS_METHOD));
+ }
+}
diff --git a/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/reflect/accessor/ConstructorAccessorTest.java b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/reflect/accessor/ConstructorAccessorTest.java
new file mode 100644
index 000000000..f566f277c
--- /dev/null
+++ b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/reflect/accessor/ConstructorAccessorTest.java
@@ -0,0 +1,59 @@
+package dev.imprex.orebfuscator.reflect.accessor;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.List;
+import org.junit.jupiter.api.Test;
+
+class ConstructorAccessorTest {
+
+ @Test
+ @SuppressWarnings("deprecation")
+ void testWrapConstructor() throws Exception {
+ var constructor = ConstructorTest.class.getDeclaredConstructor(String.class);
+ assertFalse(constructor.isAccessible());
+
+ var accessor = Accessors.wrap(constructor);
+
+ assertFalse(constructor.isAccessible());
+ assertEquals(constructor, accessor.member());
+ }
+
+ @Test
+ void testInvoke() throws Exception {
+ var constructor = ConstructorTest.class.getDeclaredConstructor(String.class);
+ var accessor = Accessors.wrap(constructor);
+
+ var instance = (ConstructorTest) accessor.invoke("a");
+ assertEquals(List.of("a"), instance.values);
+
+ assertThrows(IllegalStateException.class, () -> accessor.invoke(1));
+ }
+
+ @Test
+ void testInvokeVarargs() throws Exception {
+ var constructor = ConstructorTest.class.getDeclaredConstructor(String[].class);
+ var accessor = Accessors.wrap(constructor);
+
+ var values = new String[]{"a", "b", "c"};
+ var instance = (ConstructorTest) accessor.invoke(new Object[]{values});
+ assertEquals(List.of(values), instance.values);
+
+ assertThrows(IllegalStateException.class, () -> accessor.invoke(1));
+ }
+
+ public static class ConstructorTest {
+
+ final List values;
+
+ public ConstructorTest(String value) {
+ this.values = List.of(value);
+ }
+
+ public ConstructorTest(String... values) {
+ this.values = List.of(values);
+ }
+ }
+}
diff --git a/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/reflect/accessor/FieldAccessorTest.java b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/reflect/accessor/FieldAccessorTest.java
new file mode 100644
index 000000000..0ed961048
--- /dev/null
+++ b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/reflect/accessor/FieldAccessorTest.java
@@ -0,0 +1,88 @@
+package dev.imprex.orebfuscator.reflect.accessor;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+class FieldAccessorTest {
+
+ @Test
+ @SuppressWarnings("deprecation")
+ void testWrapField() throws Exception {
+ var field = FieldTest.class.getDeclaredField("value");
+ assertFalse(field.isAccessible());
+
+ var accessor = Accessors.wrap(field);
+
+ assertFalse(field.isAccessible());
+ assertEquals(field, accessor.member());
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ void testWrapStaticField() throws Exception {
+ var field = FieldTest.class.getDeclaredField("staticValue");
+ assertFalse(field.isAccessible());
+
+ var accessor = Accessors.wrap(field);
+
+ assertFalse(field.isAccessible());
+ assertEquals(field, accessor.member());
+ }
+
+ @Test
+ void testGet() throws Exception {
+ var field = FieldTest.class.getDeclaredField("value");
+ var accessor = Accessors.wrap(field);
+
+ var instance = new FieldTest(1);
+ assertEquals(instance.value, accessor.get(instance));
+ assertThrows(IllegalStateException.class, () -> accessor.get(null));
+ }
+
+ @Test
+ void testSet() throws Exception {
+ var field = FieldTest.class.getDeclaredField("value");
+ var accessor = Accessors.wrap(field);
+
+ var instance = new FieldTest(1);
+ accessor.set(instance, -1);
+ assertEquals(-1, instance.value);
+ assertThrows(IllegalStateException.class, () -> accessor.set(null, -1));
+ }
+
+ @Test
+ void testStaticGet() throws Exception {
+ var field = FieldTest.class.getDeclaredField("staticValue");
+ var accessor = Accessors.wrap(field);
+
+ FieldTest.staticValue = 1;
+ assertEquals(FieldTest.staticValue, accessor.get(null));
+ assertEquals(FieldTest.staticValue, accessor.get("ab"));
+ }
+
+ @Test
+ void testStaticSet() throws Exception {
+ var field = FieldTest.class.getDeclaredField("staticValue");
+ var accessor = Accessors.wrap(field);
+
+ accessor.set(null, -1);
+ assertEquals(-1, FieldTest.staticValue);
+
+ accessor.set("ab", -2);
+ assertEquals(-2, FieldTest.staticValue);
+ }
+
+ public static class FieldTest {
+
+ public static int staticValue;
+
+ public int value;
+
+ public FieldTest(int value) {
+ this.value = value;
+ }
+ }
+}
diff --git a/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/reflect/accessor/MethodAccessorTest.java b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/reflect/accessor/MethodAccessorTest.java
new file mode 100644
index 000000000..ff48df2d1
--- /dev/null
+++ b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/reflect/accessor/MethodAccessorTest.java
@@ -0,0 +1,92 @@
+package dev.imprex.orebfuscator.reflect.accessor;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.Arrays;
+import org.junit.jupiter.api.Test;
+
+class MethodAccessorTest {
+
+ @Test
+ @SuppressWarnings("deprecation")
+ void testWrapMethod() throws Exception {
+ var method = MethodTest.class.getMethod("sum", int.class, int.class);
+ assertFalse(method.isAccessible());
+
+ var accessor = Accessors.wrap(method);
+
+ assertFalse(method.isAccessible());
+ assertEquals(method, accessor.member());
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ void testWrapStaticMethod() throws Exception {
+ var method = MethodTest.class.getMethod("staticSum", int.class, int.class);
+ assertFalse(method.isAccessible());
+
+ var accessor = Accessors.wrap(method);
+
+ assertFalse(method.isAccessible());
+ assertEquals(method, accessor.member());
+ }
+
+ @Test
+ void testInvoke() throws Exception {
+ var method = MethodTest.class.getDeclaredMethod("sum", int.class, int.class);
+ var accessor = Accessors.wrap(method);
+
+ assertEquals(3, accessor.invoke(MethodTest.INSTANCE, 1, 2));
+ assertThrows(IllegalStateException.class, () -> accessor.invoke(null, 1, 2));
+ }
+
+ @Test
+ void testInvokeVarargs() throws Exception {
+ var method = MethodTest.class.getDeclaredMethod("sum", int.class, int[].class);
+ var accessor = Accessors.wrap(method);
+
+ assertEquals(10, accessor.invoke(MethodTest.INSTANCE, 1, new int[]{2, 3, 4}));
+ assertThrows(IllegalStateException.class, () -> accessor.invoke(null, 1, new int[]{2, 3, 4}));
+ }
+
+ @Test
+ void testStaticInvoke() throws Exception {
+ var method = MethodTest.class.getDeclaredMethod("staticSum", int.class, int.class);
+ var accessor = Accessors.wrap(method);
+
+ assertEquals(3, accessor.invoke(null, 1, 2));
+ assertEquals(3, accessor.invoke("ab", 1, 2));
+ }
+
+ @Test
+ void testStaticInvokeVarargs() throws Exception {
+ var method = MethodTest.class.getDeclaredMethod("staticSum", int.class, int[].class);
+ var accessor = Accessors.wrap(method);
+
+ assertEquals(10, accessor.invoke(null, 1, new int[]{2, 3, 4}));
+ assertEquals(10, accessor.invoke("ab", 1, new int[]{2, 3, 4}));
+ }
+
+ public static class MethodTest {
+
+ public static final MethodTest INSTANCE = new MethodTest();
+
+ public static int staticSum(int a, int b) {
+ return a + b;
+ }
+
+ public static int staticSum(int a, int... ints) {
+ return a + Arrays.stream(ints).sum();
+ }
+
+ public int sum(int a, int b) {
+ return a + b;
+ }
+
+ public int sum(int a, int... ints) {
+ return a + Arrays.stream(ints).sum();
+ }
+ }
+}
diff --git a/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/reflect/dummy/Entity.java b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/reflect/dummy/Entity.java
new file mode 100644
index 000000000..53bb1e741
--- /dev/null
+++ b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/reflect/dummy/Entity.java
@@ -0,0 +1,57 @@
+package dev.imprex.orebfuscator.reflect.dummy;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Objects;
+
+public class Entity implements Id {
+
+ public static final String DEFAULT_WOLRD = "default";
+
+ private static int nextEntityId = 0;
+
+ public static int nextId() {
+ return nextEntityId++;
+ }
+
+ protected final int entityId;
+
+ private volatile String world;
+ private int position;
+
+ public Entity(String world) {
+ this.entityId = nextId();
+ this.world = world;
+ }
+
+ @Override
+ public final int id() {
+ return entityId;
+ }
+
+ public String getWorld() {
+ return world;
+ }
+
+ public int getPosition() {
+ return position;
+ }
+
+ public synchronized boolean teleport(String world, int position) throws IOException, NullPointerException {
+ Objects.requireNonNull(world);
+
+ try (InputStream inputStream = Files.newInputStream(Paths.get(world))) {
+ return position == 0;
+ }
+ }
+
+ public void destroy() throws NullPointerException, ClassNotFoundException {
+ this.world = null;
+ }
+
+ protected void move(int offset) {
+ this.position += offset;
+ }
+}
diff --git a/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/reflect/dummy/Id.java b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/reflect/dummy/Id.java
new file mode 100644
index 000000000..85ab195f8
--- /dev/null
+++ b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/reflect/dummy/Id.java
@@ -0,0 +1,7 @@
+package dev.imprex.orebfuscator.reflect.dummy;
+
+public interface Id {
+
+ int id();
+
+}
diff --git a/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/reflect/dummy/Player.java b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/reflect/dummy/Player.java
new file mode 100644
index 000000000..5c690ce3b
--- /dev/null
+++ b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/reflect/dummy/Player.java
@@ -0,0 +1,28 @@
+package dev.imprex.orebfuscator.reflect.dummy;
+
+public class Player extends Entity {
+
+ private float health = 20f;
+
+ public Player() {
+ this(Entity.DEFAULT_WOLRD);
+ }
+
+ public Player(String world) {
+ super(world);
+ }
+
+ public void heal(float health) {
+ health += health;
+ }
+
+ public boolean damage(float damage) {
+ health -= damage;
+ return health > 0;
+ }
+
+ @Override
+ protected void move(int offset) {
+ super.move(offset + 1);
+ }
+}
diff --git a/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/reflect/predicate/AbstractExecutablePredicateTest.java b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/reflect/predicate/AbstractExecutablePredicateTest.java
new file mode 100644
index 000000000..b1bdf3e46
--- /dev/null
+++ b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/reflect/predicate/AbstractExecutablePredicateTest.java
@@ -0,0 +1,75 @@
+package dev.imprex.orebfuscator.reflect.predicate;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.IOException;
+import org.junit.jupiter.api.Test;
+import dev.imprex.orebfuscator.reflect.Reflector;
+import dev.imprex.orebfuscator.reflect.dummy.Entity;
+
+class AbstractExecutablePredicateTest {
+
+ @Test
+ void testException() {
+ Reflector reflector = Reflector.of(Entity.class);
+
+ assertEquals(1, reflector.method()
+ .exception(0).is(NullPointerException.class)
+ .stream().count());
+ assertEquals(1, reflector.method()
+ .exception(1).is(NullPointerException.class)
+ .stream().count());
+ assertEquals(0, reflector.method()
+ .exception(2).is(NullPointerException.class)
+ .stream().count());
+
+ assertEquals(2, reflector.method()
+ .exception().is(NullPointerException.class)
+ .stream().count());
+ assertEquals(1, reflector.method()
+ .exception().is(NullPointerException.class)
+ .exception().is(IOException.class)
+ .stream().count());
+ }
+
+ @Test
+ void testParameter() {
+ Reflector reflector = Reflector.of(Entity.class);
+
+ assertEquals(1, reflector.method()
+ .parameter(0).is(int.class)
+ .stream().count());
+ assertEquals(1, reflector.method()
+ .parameter(1).is(int.class)
+ .stream().count());
+ assertEquals(0, reflector.method()
+ .parameter(2).is(int.class)
+ .stream().count());
+
+ assertEquals(2, reflector.method()
+ .parameter().is(int.class)
+ .stream().count());
+ assertEquals(1, reflector.method()
+ .parameter().is(int.class)
+ .parameter().is(String.class)
+ .stream().count());
+ }
+
+ @Test
+ void testParameterCount() {
+ Reflector reflector = Reflector.of(Entity.class);
+
+ assertEquals(5, reflector.method()
+ .parameterCount(0)
+ .stream().count());
+ assertEquals(1, reflector.method()
+ .parameterCount(1)
+ .stream().count());
+ assertEquals(1, reflector.method()
+ .parameterCount(2)
+ .stream().count());
+ assertEquals(0, reflector.method()
+ .parameterCount(3)
+ .stream().count());
+ }
+}
diff --git a/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/reflect/predicate/AbstractMemberPredicateTest.java b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/reflect/predicate/AbstractMemberPredicateTest.java
new file mode 100644
index 000000000..84518b126
--- /dev/null
+++ b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/reflect/predicate/AbstractMemberPredicateTest.java
@@ -0,0 +1,158 @@
+package dev.imprex.orebfuscator.reflect.predicate;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.lang.reflect.Modifier;
+import java.util.regex.Pattern;
+import org.junit.jupiter.api.Test;
+import dev.imprex.orebfuscator.reflect.Reflector;
+import dev.imprex.orebfuscator.reflect.dummy.Entity;
+import dev.imprex.orebfuscator.reflect.dummy.Player;
+import dev.imprex.orebfuscator.reflect.predicate.ClassPredicate.IsClassPredicate;
+
+class AbstractMemberPredicateTest {
+
+ @Test
+ void testRequireModifier() throws Exception {
+ Reflector reflector = Reflector.of(Entity.class);
+
+ assertEquals(1, reflector.field()
+ .requireModifier(Modifier.VOLATILE)
+ .stream().count());
+ assertEquals(1, reflector.field()
+ .requirePublic()
+ .stream().count());
+ assertEquals(1, reflector.field()
+ .requireProtected()
+ .stream().count());
+ assertEquals(3, reflector.field()
+ .requirePrivate()
+ .stream().count());
+ assertEquals(2, reflector.field()
+ .requireStatic()
+ .stream().count());
+ assertEquals(2, reflector.field()
+ .requireFinal()
+ .stream().count());
+ }
+
+ @Test
+ void testBanModifier() throws Exception {
+ Reflector reflector = Reflector.of(Entity.class);
+
+ assertEquals(4, reflector.field()
+ .banModifier(Modifier.VOLATILE)
+ .stream().count());
+ assertEquals(4, reflector.field()
+ .banPublic()
+ .stream().count());
+ assertEquals(4, reflector.field()
+ .banProtected()
+ .stream().count());
+ assertEquals(2, reflector.field()
+ .banPrivate()
+ .stream().count());
+ assertEquals(3, reflector.field()
+ .banStatic()
+ .stream().count());
+ assertEquals(3, reflector.field()
+ .banFinal()
+ .stream().count());
+ }
+
+ @Test
+ void testName() throws Exception {
+ Reflector reflector = Reflector.of(Entity.class);
+
+ assertEquals(2, reflector.field()
+ .nameRegex(Pattern.compile(".*id", Pattern.CASE_INSENSITIVE | Pattern.DOTALL))
+ .stream().count());
+ assertEquals(1, reflector.field()
+ .nameIs("entityId")
+ .stream().count());
+ assertEquals(1, reflector.field()
+ .nameIsIgnoreCase("entityid")
+ .stream().count());
+ }
+
+ @Test
+ void testDeclaringClass() throws Exception {
+ Reflector reflector = Reflector.of(Entity.class);
+
+ assertEquals(5, reflector.field()
+ .declaringClass().is(Entity.class)
+ .stream().count());
+ assertEquals(5, reflector.field()
+ .declaringClass(new IsClassPredicate(Entity.class))
+ .stream().count());
+
+ assertEquals(0, reflector.field()
+ .declaringClass().is(Player.class)
+ .stream().count());
+ assertEquals(0, reflector.field()
+ .declaringClass(new IsClassPredicate(Player.class))
+ .stream().count());
+ }
+
+ @Test
+ void testAll() throws Exception {
+ Reflector reflector = Reflector.of(Entity.class);
+
+ assertEquals(1, reflector.constructor().stream().count());
+ assertEquals(5, reflector.field().stream().count());
+ assertEquals(7, reflector.method().stream().count());
+ }
+
+ @Test
+ void testGet() throws Exception {
+ Reflector reflector = Reflector.of(Entity.class);
+
+ assertNotNull(reflector.constructor().get(0));
+ assertNull(reflector.constructor().get(1));
+ }
+
+ @Test
+ void testGetOrThrow() throws Exception {
+ Reflector reflector = Reflector.of(Entity.class);
+
+ assertDoesNotThrow(() -> reflector.constructor().getOrThrow(0));
+ assertThrows(IllegalArgumentException.class, () -> reflector.constructor().getOrThrow(1));
+ }
+
+ @Test
+ void testFirst() throws Exception {
+ Reflector reflector = Reflector.of(Entity.class);
+
+ assertNotNull(reflector.constructor().first());
+ assertNull(reflector.constructor().parameterCount(0).first());
+ }
+
+ @Test
+ void testFirstOrThrow() throws Exception {
+ Reflector reflector = Reflector.of(Entity.class);
+
+ assertDoesNotThrow(() -> reflector.constructor().firstOrThrow());
+ assertThrows(IllegalArgumentException.class, () -> reflector.constructor().parameterCount(0).firstOrThrow());
+ }
+
+ @Test
+ void testFind() throws Exception {
+ Reflector reflector = Reflector.of(Entity.class);
+
+ assertNotNull(reflector.constructor().find(c -> c.getParameterCount() != 0));
+ assertNull(reflector.constructor().find(c -> c.getParameterCount() == 0));
+ }
+
+ @Test
+ void testFindOrThrow() throws Exception {
+ Reflector reflector = Reflector.of(Entity.class);
+
+ assertDoesNotThrow(() -> reflector.constructor().findOrThrow(c -> c.getParameterCount() != 0));
+ assertThrows(IllegalArgumentException.class,
+ () -> reflector.constructor().findOrThrow(c -> c.getParameterCount() == 0));
+ }
+}
diff --git a/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/reflect/predicate/ClassPredicateTest.java b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/reflect/predicate/ClassPredicateTest.java
new file mode 100644
index 000000000..c4de77416
--- /dev/null
+++ b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/reflect/predicate/ClassPredicateTest.java
@@ -0,0 +1,109 @@
+package dev.imprex.orebfuscator.reflect.predicate;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.regex.Pattern;
+import org.junit.jupiter.api.Test;
+import dev.imprex.orebfuscator.reflect.dummy.Entity;
+import dev.imprex.orebfuscator.reflect.dummy.Id;
+import dev.imprex.orebfuscator.reflect.dummy.Player;
+
+class ClassPredicateTest {
+
+ @Test
+ void testIsClassPredicate() {
+ ClassPredicate predicate = new ClassPredicate.IsClassPredicate(Entity.class);
+
+ assertFalse(predicate.test(Id.class));
+ assertTrue(predicate.test(Entity.class));
+ assertFalse(predicate.test(Player.class));
+
+ assertThrows(NullPointerException.class, () -> new ClassPredicate.IsClassPredicate(null));
+ assertThrows(NullPointerException.class, () -> predicate.test(null));
+
+ assertEquals("{is dev.imprex.orebfuscator.reflect.dummy.Entity}", predicate.requirement());
+ }
+
+ @Test
+ void testSuperClassPredicate() {
+ ClassPredicate predicate = new ClassPredicate.SuperClassPredicate(Entity.class);
+
+ assertTrue(predicate.test(Id.class));
+ assertTrue(predicate.test(Entity.class));
+ assertFalse(predicate.test(Player.class));
+
+ assertThrows(NullPointerException.class, () -> new ClassPredicate.SuperClassPredicate(null));
+ assertThrows(NullPointerException.class, () -> predicate.test(null));
+
+ assertEquals("{super-class-of dev.imprex.orebfuscator.reflect.dummy.Entity}", predicate.requirement());
+ }
+
+ @Test
+ void testSubClassPredicate() {
+ ClassPredicate predicate = new ClassPredicate.SubClassPredicate(Entity.class);
+
+ assertFalse(predicate.test(Id.class));
+ assertTrue(predicate.test(Entity.class));
+ assertTrue(predicate.test(Player.class));
+
+ assertThrows(NullPointerException.class, () -> new ClassPredicate.SubClassPredicate(null));
+ assertThrows(NullPointerException.class, () -> predicate.test(null));
+
+ assertEquals("{sub-class-of dev.imprex.orebfuscator.reflect.dummy.Entity}", predicate.requirement());
+ }
+
+ @Test
+ void testAnyClassPredicate() {
+ // we use a LinkedHashSet here to preserve iteration order for the requirement string
+ Set> set = new LinkedHashSet<>(List.of(Id.class, Entity.class));
+ ClassPredicate predicate = new ClassPredicate.AnyClassPredicate(set);
+
+ assertTrue(predicate.test(Id.class));
+ assertTrue(predicate.test(Entity.class));
+ assertFalse(predicate.test(Player.class));
+
+ assertThrows(NullPointerException.class, () -> new ClassPredicate.AnyClassPredicate(null));
+ assertThrows(NullPointerException.class, () -> predicate.test(null));
+
+ assertEquals("{any (dev.imprex.orebfuscator.reflect.dummy.Id, dev.imprex.orebfuscator.reflect.dummy.Entity)}",
+ predicate.requirement());
+ }
+
+ @Test
+ void testRegexClassPredicate() {
+ Pattern pattern = Pattern.compile(".*y", Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
+ ClassPredicate predicate = new ClassPredicate.RegexClassPredicate(pattern);
+
+ assertFalse(predicate.test(Id.class));
+ assertTrue(predicate.test(Entity.class));
+ assertFalse(predicate.test(Player.class));
+
+ assertThrows(NullPointerException.class, () -> new ClassPredicate.RegexClassPredicate(null));
+ assertThrows(NullPointerException.class, () -> predicate.test(null));
+
+ assertEquals("{regex .*y}", predicate.requirement());
+ }
+
+ @Test
+ void testBuilder() {
+ ClassPredicate.Builder builder = new ClassPredicate.Builder<>(Function.identity());
+
+ assertEquals(new ClassPredicate.IsClassPredicate(Entity.class), builder.is(Entity.class));
+ assertEquals(new ClassPredicate.SuperClassPredicate(Entity.class), builder.superOf(Entity.class));
+ assertEquals(new ClassPredicate.SubClassPredicate(Entity.class), builder.subOf(Entity.class));
+
+ ClassPredicate expected = new ClassPredicate.AnyClassPredicate(Set.of(Id.class, Entity.class));
+ assertEquals(expected, builder.any(Id.class, Entity.class));
+ assertEquals(expected, builder.any(Set.of(Id.class, Entity.class)));
+
+ Pattern pattern = Pattern.compile("foo");
+ assertEquals(new ClassPredicate.RegexClassPredicate(pattern), builder.regex(pattern));
+ }
+}
diff --git a/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/reflect/predicate/FieldPredicateTest.java b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/reflect/predicate/FieldPredicateTest.java
new file mode 100644
index 000000000..d8ccc33d8
--- /dev/null
+++ b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/reflect/predicate/FieldPredicateTest.java
@@ -0,0 +1,22 @@
+package dev.imprex.orebfuscator.reflect.predicate;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+import dev.imprex.orebfuscator.reflect.Reflector;
+import dev.imprex.orebfuscator.reflect.dummy.Entity;
+
+class FieldPredicateTest {
+
+ @Test
+ void testType() {
+ Reflector reflector = Reflector.of(Entity.class);
+
+ assertEquals(3, reflector.field()
+ .type().is(int.class)
+ .stream().count());
+ assertEquals(2, reflector.field()
+ .type().is(String.class)
+ .stream().count());
+ }
+}
diff --git a/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/reflect/predicate/MethodPredicateTest.java b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/reflect/predicate/MethodPredicateTest.java
new file mode 100644
index 000000000..85e3efa27
--- /dev/null
+++ b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/reflect/predicate/MethodPredicateTest.java
@@ -0,0 +1,25 @@
+package dev.imprex.orebfuscator.reflect.predicate;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+import dev.imprex.orebfuscator.reflect.Reflector;
+import dev.imprex.orebfuscator.reflect.dummy.Entity;
+
+class MethodPredicateTest {
+
+ @Test
+ void testReturnType() {
+ Reflector reflector = Reflector.of(Entity.class);
+
+ assertEquals(2, reflector.method()
+ .returnType().is(void.class)
+ .stream().count());
+ assertEquals(3, reflector.method()
+ .returnType().is(int.class)
+ .stream().count());
+ assertEquals(1, reflector.method()
+ .returnType().is(String.class)
+ .stream().count());
+ }
+}
diff --git a/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/util/BlockPosTest.java b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/util/BlockPosTest.java
new file mode 100644
index 000000000..54fdc2ba3
--- /dev/null
+++ b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/util/BlockPosTest.java
@@ -0,0 +1,44 @@
+package dev.imprex.orebfuscator.util;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+public class BlockPosTest {
+
+ @Test
+ public void testLongFormat() {
+ BlockPos positionA = new BlockPos(-52134, BlockPos.MAX_Y, 6243234);
+ BlockPos positionB = new BlockPos(0, BlockPos.MIN_Y, -4);
+ BlockPos positionC = new BlockPos(15, 0, -5663423);
+ BlockPos positionD = new BlockPos(21523, 16, -5663423);
+
+ long valueA = positionA.toLong();
+ long valueB = positionB.toLong();
+ long valueC = positionC.toLong();
+ long valueD = positionD.toLong();
+
+ assertEquals(positionA, BlockPos.fromLong(valueA));
+ assertEquals(positionB, BlockPos.fromLong(valueB));
+ assertEquals(positionC, BlockPos.fromLong(valueC));
+ assertEquals(positionD, BlockPos.fromLong(valueD));
+ }
+
+ @Test
+ public void testSectionPos() {
+ final int chunkX = -42 << 4;
+ final int chunkZ = 6521 << 4;
+
+ BlockPos positionA = new BlockPos(chunkX + 8, BlockPos.MAX_Y, chunkZ);
+ BlockPos positionB = new BlockPos(chunkX, BlockPos.MIN_Y, chunkZ + 15);
+ BlockPos positionC = new BlockPos(chunkX + 15, 0, chunkZ + 4);
+
+ int sectionPosA = positionA.toSectionPos();
+ int sectionPosB = positionB.toSectionPos();
+ int sectionPosC = positionC.toSectionPos();
+
+ assertEquals(positionA, BlockPos.fromSectionPos(chunkX, chunkZ, sectionPosA));
+ assertEquals(positionB, BlockPos.fromSectionPos(chunkX, chunkZ, sectionPosB));
+ assertEquals(positionC, BlockPos.fromSectionPos(chunkX, chunkZ, sectionPosC));
+ }
+}
diff --git a/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/util/WeightedRandomTest.java b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/util/WeightedRandomTest.java
new file mode 100644
index 000000000..7dd6e5d0d
--- /dev/null
+++ b/orebfuscator-core/src/test/java/dev/imprex/orebfuscator/util/WeightedRandomTest.java
@@ -0,0 +1,128 @@
+package dev.imprex.orebfuscator.util;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import org.junit.jupiter.api.Test;
+
+public class WeightedRandomTest {
+
+ private static final long SEED = 1337;
+ private static final int SAMPLES = 250_000;
+ private static final double SIGMA = 2.0;
+
+ private WeightedRandom createWeightedRandom(Map weights) {
+ var builder = WeightedRandom.builder();
+ for (var entry : weights.entrySet()) {
+ builder.add(entry.getKey(), entry.getValue());
+ }
+ return builder.build();
+ }
+
+ private void assertDistribution(WeightedRandom weightedRandom, Map weights, int n, long seed,
+ double sigma) {
+ Random random = new Random(seed);
+
+ Map counts = new HashMap<>();
+ for (int i = 0; i < n; i++) {
+ counts.merge(weightedRandom.next(random), 1, Integer::sum);
+ }
+
+ double totalWeight = weights.values().stream().mapToDouble(Double::doubleValue).sum();
+
+ for (var entry : weights.entrySet()) {
+ int key = entry.getKey();
+ double prob = entry.getValue() / totalWeight;
+ int count = counts.getOrDefault(key, 0);
+ double observed = count / (double) n;
+
+ // Binomial standard deviation for frequency
+ double sd = Math.sqrt(prob * (1 - prob) / n);
+ double tol = sigma * sd;
+
+ assertTrue(Math.abs(observed - prob) <= tol, () -> String
+ .format("freq for %d off: obs=%.6f exp=%.6f tol=%.6f (N=%d, sd=%.6g)", key, observed, prob, tol, n, sd));
+ }
+ }
+
+ @Test
+ public void testEmptyBuilderThrows() {
+ var builder = WeightedRandom.builder();
+ assertThrows(IllegalStateException.class, builder::build);
+ }
+
+ @Test
+ public void testInvalidWeightsThrow() {
+ var builder = WeightedRandom.builder();
+ assertThrows(IllegalArgumentException.class, () -> builder.add(0, 0.0));
+ assertThrows(IllegalArgumentException.class, () -> builder.add(0, -1.0));
+ assertThrows(IllegalArgumentException.class, () -> builder.add(0, Double.POSITIVE_INFINITY));
+ assertThrows(IllegalArgumentException.class, () -> builder.add(0, Double.NEGATIVE_INFINITY));
+ }
+
+ @Test
+ public void testSingleEntry() {
+ var weightedRandom = WeightedRandom.builder().add(42, 1.0).build();
+
+ for (int i = 0; i < 1000; i++) {
+ assertEquals(42, weightedRandom.next());
+ }
+ }
+
+ @Test
+ public void testSameSeeds() {
+ var distribution = Map.of(1, 0.1, 2, 0.2, 3, 0.9);
+ var weightedRandom = createWeightedRandom(distribution);
+
+ Random randomA = new Random(42);
+ Random randomB = new Random(42);
+
+ List sequenceA = new ArrayList<>();
+ List sequenceB = new ArrayList<>();
+ for (int i = 0; i < 50; i++) {
+ sequenceA.add(weightedRandom.next(randomA));
+ sequenceB.add(weightedRandom.next(randomB));
+ }
+
+ assertEquals(sequenceA, sequenceB, "Sequences with same seed should match exactly");
+ }
+
+ @Test
+ public void testUniformWeights() {
+ var distribution = Map.of(1, 1.0, 2, 1.0, 3, 1.0, 4, 1.0);
+ var weightedRandom = createWeightedRandom(distribution);
+ assertDistribution(weightedRandom, distribution, SAMPLES, SEED, SIGMA);
+ }
+
+ @Test
+ public void testRandomWeights() {
+ var distribution = Map.of(1, 0.1, 2, 0.2, 3, 0.9);
+ var weightedRandom = createWeightedRandom(distribution);
+ assertDistribution(weightedRandom, distribution, SAMPLES, SEED, SIGMA);
+ }
+
+ @Test
+ public void testMergedWeights() {
+ var weightedRandom = WeightedRandom.builder()
+ .add(1, 0.1)
+ .add(2, 0.3)
+ .add(1, 0.9)
+ .build();
+
+ var distribution = Map.of(1, 1.0, 2, 0.3);
+ assertDistribution(weightedRandom, distribution, SAMPLES, SEED, SIGMA);
+ }
+
+ @Test
+ public void testLargeWeights() {
+ var distribution = Map.of(1, 0.1, 2, 0.2, 3, 100.0, 4, 0.5);
+ var weightedRandom = createWeightedRandom(distribution);
+ assertDistribution(weightedRandom, distribution, SAMPLES, SEED, SIGMA);
+ }
+}
diff --git a/orebfuscator-core/src/test/resources/config/context-test-report.txt b/orebfuscator-core/src/test/resources/config/context-test-report.txt
new file mode 100644
index 000000000..82fb594b9
--- /dev/null
+++ b/orebfuscator-core/src/test/resources/config/context-test-report.txt
@@ -0,0 +1,16 @@
+[31;1mEncountered 9 issue(s) while parsing the config:
+[m[31;1m- is missing or empty
+[33;1m- is missing or empty
+[33;1mchild:
+ [31;1m- is missing or empty
+ [33;1m- is missing or empty
+ [33;1m- got disabled due to errors
+ [33;1ma:
+ [31;1m- is missing or empty
+ [33;1m- is missing or empty
+[33;1ma:
+ [33;1mb:
+ [33;1mc:
+ [31;1m- is missing or empty
+ [33;1m- is missing or empty
+[m
\ No newline at end of file
diff --git a/orebfuscator-core/src/test/resources/config/example-config.yml b/orebfuscator-core/src/test/resources/config/example-config.yml
new file mode 100644
index 000000000..b9ddb9f07
--- /dev/null
+++ b/orebfuscator-core/src/test/resources/config/example-config.yml
@@ -0,0 +1,13 @@
+# boolean start
+boolean: true # boolean inline
+# section start
+section: # section inline
+ # string start
+ string: abc # string inline
+ # list start
+ list:
+ - a # inline a 1
+ # inline a 2
+ - b # inline b
+ - c # inline c
+# config end
diff --git a/orebfuscator-nms/orebfuscator-nms-api/pom.xml b/orebfuscator-nms/orebfuscator-nms-api/pom.xml
index 90338621f..172b872c8 100644
--- a/orebfuscator-nms/orebfuscator-nms-api/pom.xml
+++ b/orebfuscator-nms/orebfuscator-nms-api/pom.xml
@@ -18,19 +18,10 @@
provided