diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml
index e9f4d842..6aced9ef 100644
--- a/.gitea/workflows/build.yml
+++ b/.gitea/workflows/build.yml
@@ -51,6 +51,7 @@ jobs:
rm -rf deploy
mkdir -p deploy
+ cp "AccessWidener/build/libs/AccessWidener-all.jar" "deploy/AccessWidener.jar"
cp "BauSystem/build/libs/BauSystem-all.jar" "deploy/BauSystem.jar"
cp "FightSystem/build/libs/FightSystem-all.jar" "deploy/FightSystem.jar"
cp "KotlinCore/build/libs/KotlinCore-all.jar" "deploy/KotlinCore.jar"
diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/FlatteningWrapper.java b/AccessWidener/build.gradle.kts
similarity index 59%
rename from SpigotCore/SpigotCore_Main/src/de/steamwar/core/FlatteningWrapper.java
rename to AccessWidener/build.gradle.kts
index 6d6d4d1b..a6632cf1 100644
--- a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/FlatteningWrapper.java
+++ b/AccessWidener/build.gradle.kts
@@ -17,18 +17,29 @@
* along with this program. If not, see .
*/
-package de.steamwar.core;
+plugins {
+ `java-library`
+ alias(libs.plugins.shadow)
+}
-import lombok.experimental.UtilityClass;
-import net.minecraft.network.chat.MutableComponent;
-import net.minecraft.network.chat.contents.PlainTextContents;
+dependencies {
+ implementation("org.ow2.asm:asm:9.7")
+ implementation("org.ow2.asm:asm-commons:9.7")
+}
-import java.util.Optional;
-
-@UtilityClass
-public class FlatteningWrapper {
-
- public Object formatDisplayName(String displayName) {
- return displayName != null ? Optional.of((Object) MutableComponent.create(PlainTextContents.create(displayName))) : Optional.empty();
+tasks.shadowJar {
+ manifest {
+ attributes(
+ "Manifest-Version" to "1.0",
+ "Build-Jdk-Spec" to "21",
+ "Main-Class" to "de.steamwar.Main",
+ "Premain-Class" to "de.steamwar.Agent",
+ "Can-Retransform-Classes" to "true",
+ "Can-Redefine-Classes" to "true",
+ )
}
}
+
+tasks.build {
+ dependsOn(tasks.shadowJar)
+}
diff --git a/AccessWidener/src/main/java/de/steamwar/AccessWidenerEntry.java b/AccessWidener/src/main/java/de/steamwar/AccessWidenerEntry.java
new file mode 100644
index 00000000..5b5ff9a0
--- /dev/null
+++ b/AccessWidener/src/main/java/de/steamwar/AccessWidenerEntry.java
@@ -0,0 +1,49 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2026 SteamWar.de-Serverteam
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar;
+
+/**
+ * A single parsed line from a .accesswidener file.
+ *
+ * Examples:
+ * accessible class net/minecraft/server/level/ServerPlayer
+ * accessible method net/minecraft/server/level/ServerPlayer getStats ()V
+ * mutable field net/minecraft/world/entity/Entity id I
+ * extendable class net/minecraft/world/level/chunk/LevelChunk
+ */
+public record AccessWidenerEntry(
+ /** accessible | mutable | extendable (may have "transitive-" prefix) */
+ String directive,
+ /** class | method | field */
+ String memberType,
+ /** Internal class name, e.g. net/minecraft/server/level/ServerPlayer */
+ String target,
+ /** Method/field name, null for class entries */
+ String name,
+ /** Descriptor, null for class entries */
+ String descriptor) {
+ /**
+ * Returns true if this entry targets the class with the given internal name.
+ */
+ public boolean targets(String internalName) {
+ return target.equals(internalName);
+ }
+}
+
diff --git a/AccessWidener/src/main/java/de/steamwar/AccessWidenerParser.java b/AccessWidener/src/main/java/de/steamwar/AccessWidenerParser.java
new file mode 100644
index 00000000..d962e0dc
--- /dev/null
+++ b/AccessWidener/src/main/java/de/steamwar/AccessWidenerParser.java
@@ -0,0 +1,104 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2026 SteamWar.de-Serverteam
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Parses Fabric-compatible .accesswidener files.
+ *
+ * Supported format:
+ *
+ * accessWidener v2 named
+ *
+ * # comments are supported
+ * accessible class net/minecraft/Foo
+ * accessible method net/minecraft/Foo someMethod ()V
+ * accessible field net/minecraft/Foo someField I
+ * mutable field net/minecraft/Foo someField I
+ * extendable class net/minecraft/Foo
+ * extendable method net/minecraft/Foo someMethod ()V
+ *
+ * # transitive variants (expose widening to dependents)
+ * transitive-accessible class net/minecraft/Foo
+ *
+ */
+public final class AccessWidenerParser {
+
+ private AccessWidenerParser() {
+ }
+
+ public static List parse(InputStream in) throws IOException {
+ List entries = new ArrayList<>();
+
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
+
+ String line;
+ boolean headerSeen = false;
+
+ while ((line = reader.readLine()) != null) {
+ // Strip inline comments
+ int commentIdx = line.indexOf('#');
+ if (commentIdx >= 0) line = line.substring(0, commentIdx);
+ line = line.strip();
+
+ if (line.isEmpty()) continue;
+
+ if (!headerSeen) {
+ // First non-blank, non-comment line must be the header
+ if (!line.startsWith("accessWidener")) {
+ throw new IOException("Missing accessWidener header, got: " + line);
+ }
+ headerSeen = true;
+ continue;
+ }
+
+ AccessWidenerEntry entry = parseLine(line);
+ if (entry != null) entries.add(entry);
+ }
+ }
+
+ return entries;
+ }
+
+ private static AccessWidenerEntry parseLine(String line) {
+ String[] parts = line.split("\\s+");
+ if (parts.length < 3) return null;
+
+ String directive = parts[0]; // accessible / mutable / extendable / transitive-*
+ String memberType = parts[1]; // class / method / field
+ String target = parts[2]; // internal class name
+
+ return switch (memberType) {
+ case "class" -> new AccessWidenerEntry(directive, "class", target, null, null);
+ case "method", "field" -> {
+ if (parts.length < 5) yield null;
+ yield new AccessWidenerEntry(directive, memberType, target, parts[3], parts[4]);
+ }
+ default -> null;
+ };
+ }
+}
\ No newline at end of file
diff --git a/AccessWidener/src/main/java/de/steamwar/Agent.java b/AccessWidener/src/main/java/de/steamwar/Agent.java
new file mode 100644
index 00000000..e078d357
--- /dev/null
+++ b/AccessWidener/src/main/java/de/steamwar/Agent.java
@@ -0,0 +1,75 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2026 SteamWar.de-Serverteam
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.instrument.Instrumentation;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Logger;
+
+/**
+ * Java agent entry point.
+ *
+ *
+ */
+public class Agent {
+ private Agent() {
+ throw new IllegalStateException("Utility class");
+ }
+
+ private static final Logger LOG = Logger.getLogger("AccessWidenerAgent");
+
+ // -javaagent: startup
+ public static void premain(String args, Instrumentation inst) {
+ init(inst);
+ }
+
+ private static void init(Instrumentation inst) {
+ LOG.info("[AccessWidener] Agent initialising.");
+
+ List entries = new ArrayList<>();
+ File file = new File(new File(".").getAbsoluteFile(), "plugins/");
+ File[] files = file.listFiles();
+ if (files == null) files = new File[0];
+ for (File jarFile : files) {
+ if (!jarFile.isFile()) continue;
+ if (!jarFile.getName().endsWith(".jar")) continue;
+ try {
+ entries.addAll(Utils.findAndParseAccessWideners(jarFile.toPath()));
+ } catch (IOException e) {
+ LOG.warning("Failed to parse access wideners from " + jarFile.getAbsolutePath());
+ }
+ }
+ LOG.info("[AccessWidener] Loaded " + entries.size() + " access wideners.");
+
+ inst.addTransformer(new WideningTransformer(entries), false);
+ LOG.info("[AccessWidener] Agent ready.");
+ }
+}
+
diff --git a/AccessWidener/src/main/java/de/steamwar/ClassPatcher.java b/AccessWidener/src/main/java/de/steamwar/ClassPatcher.java
new file mode 100644
index 00000000..c54f69dd
--- /dev/null
+++ b/AccessWidener/src/main/java/de/steamwar/ClassPatcher.java
@@ -0,0 +1,83 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2026 SteamWar.de-Serverteam
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar;
+
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassWriter;
+
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Uses ASM to patch class bytecode according to a list of access widener entries.
+ *
+ * Returns {@code null} if the class is not targeted by any entry (no-op signal
+ * to the caller so it can skip the write).
+ */
+public class ClassPatcher {
+
+ private static final Logger LOG = Logger.getLogger("ClassPatcher");
+
+ private final List entries;
+
+ /** Pre-computed set of targeted internal names for fast filtering. */
+ private final Set targets;
+
+ private final Set targetsPublicConstructor;
+
+ public ClassPatcher(List entries) {
+ this.entries = entries;
+ this.targets = entries.stream()
+ .map(AccessWidenerEntry::target)
+ .flatMap(s -> {
+ if (!s.contains("$")) return Stream.of(s);
+ int index = s.lastIndexOf('$');
+ return Stream.of(s, s.substring(0, index));
+ })
+ .collect(Collectors.toSet());
+ this.targetsPublicConstructor = entries.stream()
+ .filter(entry -> entry.directive().equals("transitive-extendable"))
+ .map(AccessWidenerEntry::target)
+ .collect(Collectors.toSet());
+ }
+
+ /**
+ * Patches {@code classBytes} if {@code className} is targeted.
+ *
+ * @return patched bytes, or {@code null} if no changes were needed
+ */
+ public byte[] patch(String className, byte[] classBytes) {
+ if (!targets.contains(className)) return null;
+
+ try {
+ ClassReader cr = new ClassReader(classBytes);
+ ClassWriter cw = new ClassWriter(cr, 0);
+ cr.accept(new ClassTransformer(cw, className, entries, targetsPublicConstructor.contains(className)), ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
+ return cw.toByteArray();
+ } catch (Exception e) {
+ LOG.warning("[AccessWidener] Failed to transform " + className + ": " + e.getMessage());
+ return null;
+ }
+ }
+}
+
diff --git a/AccessWidener/src/main/java/de/steamwar/ClassTransformer.java b/AccessWidener/src/main/java/de/steamwar/ClassTransformer.java
new file mode 100644
index 00000000..4467bd48
--- /dev/null
+++ b/AccessWidener/src/main/java/de/steamwar/ClassTransformer.java
@@ -0,0 +1,125 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2026 SteamWar.de-Serverteam
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.List;
+
+public class ClassTransformer extends ClassVisitor {
+
+ private final String internalName;
+ private final List entries;
+ private final boolean appendPublicConstructor;
+
+ public ClassTransformer(ClassVisitor cv, String internalName, List entries, boolean appendPublicConstructor) {
+ super(Opcodes.ASM9, cv);
+ this.internalName = internalName;
+ this.entries = entries;
+ this.appendPublicConstructor = appendPublicConstructor;
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
+ int newAccess = access;
+ for (AccessWidenerEntry e : entries) {
+ if (!e.targets(internalName) || !"class".equals(e.memberType())) continue;
+ newAccess = applyDirective(e.directive(), newAccess, false);
+ }
+ if (appendPublicConstructor) {
+ MethodVisitor methodVisitor = visitMethod(Opcodes.ACC_PUBLIC, "", "()V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+ methodVisitor.visitMethodInsn(
+ Opcodes.INVOKESPECIAL,
+ "java/lang/Object",
+ "",
+ "()V",
+ false
+ );
+ methodVisitor.visitInsn(Opcodes.RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ super.visit(version, newAccess, name, signature, superName, interfaces);
+ }
+
+ @Override
+ public void visitInnerClass(String name, String outerName, String innerName, int access) {
+ int newAccess = access;
+ for (AccessWidenerEntry e : entries) {
+ if (!e.target().equals(name) || !"class".equals(e.memberType())) continue;
+ newAccess = applyDirective(e.directive(), newAccess, false);
+ }
+ super.visitInnerClass(name, outerName, innerName, newAccess);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
+ int newAccess = access;
+ for (AccessWidenerEntry e : entries) {
+ if (!e.targets(internalName) || !"method".equals(e.memberType())) continue;
+ if (!name.equals(e.name()) || !descriptor.equals(e.descriptor())) continue;
+ newAccess = applyDirective(e.directive(), newAccess, false);
+ }
+ return super.visitMethod(newAccess, name, descriptor, signature, exceptions);
+ }
+
+ @Override
+ public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
+ int newAccess = access;
+ for (AccessWidenerEntry e : entries) {
+ if (!e.targets(internalName) || !"field".equals(e.memberType())) continue;
+ if (!name.equals(e.name())) continue;
+ newAccess = applyDirective(e.directive(), newAccess, true);
+ }
+ return super.visitField(newAccess, name, descriptor, signature, value);
+ }
+
+ /**
+ * Apply a directive to an access bitmask.
+ *
+ * @param directive accessible / mutable / extendable (with optional "transitive-" prefix)
+ * @param access current access flags
+ * @param isField true when processing a field (mutable removes final)
+ */
+ private static int applyDirective(String directive, int access, boolean isField) {
+ // Strip transitive- prefix — the widening itself is the same
+ String effective = directive.startsWith("transitive-") ? directive.substring("transitive-".length()) : directive;
+
+ return switch (effective) {
+ case "accessible" -> makePublic(access);
+ case "extendable" -> makePublic(removeFinal(access));
+ case "mutable" -> isField ? removeFinal(access) : access;
+ default -> access;
+ };
+ }
+
+ private static int makePublic(int access) {
+ return (access & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED)) | Opcodes.ACC_PUBLIC;
+ }
+
+ private static int removeFinal(int access) {
+ return access & ~Opcodes.ACC_FINAL;
+ }
+}
diff --git a/AccessWidener/src/main/java/de/steamwar/Main.java b/AccessWidener/src/main/java/de/steamwar/Main.java
new file mode 100644
index 00000000..8dc81875
--- /dev/null
+++ b/AccessWidener/src/main/java/de/steamwar/Main.java
@@ -0,0 +1,125 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2026 SteamWar.de-Serverteam
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.*;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Command-line tool that produces a widened copy of a JAR for use as a
+ * compile-time stub in IntelliJ / Gradle.
+ *
+ * Usage:
+ * java -jar jar-widener.jar [file.accesswidener ...]
+ *
+ * The output JAR is identical to the input JAR except that every class
+ * targeted by the access widener entries has its access flags patched:
+ * accessible → public
+ * extendable → public + non-final
+ * mutable → non-final field
+ *
+ * Intended for use as a Gradle task so IntelliJ sees the already-widened
+ * class when you Ctrl+click NMS code, and javac compiles without complaints.
+ */
+public class Main {
+
+ public static void main(String[] args) throws Exception {
+ if (args.length < 2) {
+ System.err.println("Usage: jar-widener [*.accesswidener ...]");
+ System.exit(1);
+ }
+
+ Path inputJar = Path.of(args[0]);
+ Path outputJar = Path.of(args[1]);
+
+ if (!Files.exists(inputJar)) {
+ System.err.println("Input JAR not found: " + inputJar);
+ System.exit(1);
+ }
+
+ // --- Collect all access widener entries ---
+ List entries = new ArrayList<>();
+
+ if (args.length > 2) {
+ for (int i = 2; i < args.length; i++) {
+ Path awFile = Path.of(args[i]);
+ if (!Files.exists(awFile)) {
+ System.err.println("Warning: access widener file not found, skipping: " + awFile);
+ continue;
+ }
+ try (InputStream in = Files.newInputStream(awFile)) {
+ List parsed = AccessWidenerParser.parse(in);
+ System.out.println("Loaded " + parsed.size() + " entries from " + awFile.getFileName());
+ entries.addAll(parsed);
+ }
+ }
+ }
+
+ if (entries.isEmpty()) {
+ System.out.println("No access widener entries found — copying JAR unchanged.");
+ Files.createDirectories(outputJar.getParent());
+ Files.copy(inputJar, outputJar, StandardCopyOption.REPLACE_EXISTING);
+ return;
+ }
+
+ System.out.println("Widening " + inputJar.getFileName()
+ + " with " + entries.size() + " total entr"
+ + (entries.size() == 1 ? "y" : "ies") + "...");
+
+ // --- Copy input → output, transforming .class files in place ---
+ Files.createDirectories(outputJar.getParent());
+ Files.copy(inputJar, outputJar, StandardCopyOption.REPLACE_EXISTING);
+
+ ClassPatcher patcher = new ClassPatcher(entries);
+
+ try (FileSystem fs = FileSystems.newFileSystem(outputJar)) {
+ // Walk every .class entry in the JAR
+ try (var stream = Files.walk(fs.getPath("/"))) {
+ stream.filter(p -> p.toString().endsWith(".class"))
+ .forEach(classPath -> patchClass(fs, classPath, patcher));
+ }
+ }
+
+ System.out.println("Done. Widened JAR written to " + outputJar);
+ }
+
+ private static void patchClass(FileSystem fs, Path classPath, ClassPatcher patcher) {
+ // Derive internal class name from path e.g. /net/minecraft/Foo.class → net/minecraft/Foo
+ String internalName = classPath.toString()
+ .replaceFirst("^/", "")
+ .replace(".class", "");
+
+ try {
+ byte[] original = Files.readAllBytes(classPath);
+ byte[] patched = patcher.patch(internalName, original);
+
+ if (patched != null) {
+ Files.write(classPath, patched);
+ System.out.println(" Widened: " + internalName);
+ }
+ } catch (IOException e) {
+ System.err.println(" Warning: failed to patch " + internalName + ": " + e.getMessage());
+ }
+ }
+}
+
diff --git a/AccessWidener/src/main/java/de/steamwar/Utils.java b/AccessWidener/src/main/java/de/steamwar/Utils.java
new file mode 100644
index 00000000..1a5bc541
--- /dev/null
+++ b/AccessWidener/src/main/java/de/steamwar/Utils.java
@@ -0,0 +1,60 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2026 SteamWar.de-Serverteam
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.logging.Logger;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+public class Utils {
+
+ private static final Logger LOG = Logger.getLogger("AccessWidenerAgent");
+
+ private Utils() {
+ throw new IllegalStateException("Utility class");
+ }
+
+ public static List findAndParseAccessWideners(Path jarPath) throws IOException {
+ List results = new ArrayList<>();
+
+ try (ZipFile zip = new ZipFile(jarPath.toFile())) {
+ Enumeration extends ZipEntry> entries = zip.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+ if (entry.isDirectory() || !entry.getName().endsWith(".accesswidener")) continue;
+
+ try (InputStream in = zip.getInputStream(entry)) {
+ results.addAll(AccessWidenerParser.parse(in));
+ } catch (IOException e) {
+ LOG.warning("[AccessWidener] Failed to parse " + entry.getName()
+ + " in " + jarPath.getFileName() + ": " + e.getMessage());
+ }
+ }
+ }
+
+ return results;
+ }
+}
diff --git a/AccessWidener/src/main/java/de/steamwar/WideningTransformer.java b/AccessWidener/src/main/java/de/steamwar/WideningTransformer.java
new file mode 100644
index 00000000..8fcd33e2
--- /dev/null
+++ b/AccessWidener/src/main/java/de/steamwar/WideningTransformer.java
@@ -0,0 +1,44 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2026 SteamWar.de-Serverteam
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar;
+
+import java.lang.instrument.ClassFileTransformer;
+import java.security.ProtectionDomain;
+import java.util.List;
+
+/**
+ * Transforms class bytecode to apply access widening rules.
+ *
+ * Also monitors for new plugin ClassLoaders appearing (when plugins load after
+ * the agent attaches) and automatically picks up their .accesswidener files.
+ */
+public class WideningTransformer implements ClassFileTransformer {
+
+ private final ClassPatcher patcher;
+
+ public WideningTransformer(List entries) {
+ patcher = new ClassPatcher(entries);
+ }
+
+ @Override
+ public byte[] transform(ClassLoader loader, String className, Class> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
+ return patcher.patch(className, classfileBuffer);
+ }
+}
diff --git a/BauSystem/BauSystem_Main/build.gradle.kts b/BauSystem/BauSystem_Main/build.gradle.kts
index f51b33cd..7f5f9b18 100644
--- a/BauSystem/BauSystem_Main/build.gradle.kts
+++ b/BauSystem/BauSystem_Main/build.gradle.kts
@@ -19,6 +19,7 @@
plugins {
steamwar.java
+ widener
}
tasks.compileJava {
@@ -47,3 +48,7 @@ dependencies {
implementation(libs.luaj)
implementation(files("$projectDir/../libs/YAPION-SNAPSHOT.jar"))
}
+
+widener {
+ fromCatalog(libs.nms)
+}
diff --git a/BauSystem/BauSystem_Main/src/bausystem.accesswidener b/BauSystem/BauSystem_Main/src/bausystem.accesswidener
new file mode 100644
index 00000000..5dd89fdf
--- /dev/null
+++ b/BauSystem/BauSystem_Main/src/bausystem.accesswidener
@@ -0,0 +1,13 @@
+accessWidener v2 named
+
+# For NoClipCommand
+accessible field net/minecraft/server/level/ServerPlayerGameMode gameModeForPlayer Lnet/minecraft/world/level/GameType;
+
+# For PlaceItemUtils
+accessible field org/bukkit/craftbukkit/block/CraftBlockState position Lnet/minecraft/core/BlockPos;
+mutable field org/bukkit/craftbukkit/block/CraftBlockState position Lnet/minecraft/core/BlockPos;
+accessible field org/bukkit/craftbukkit/block/CraftBlockState world Lorg/bukkit/craftbukkit/CraftWorld;
+mutable field org/bukkit/craftbukkit/block/CraftBlockState world Lorg/bukkit/craftbukkit/CraftWorld;
+
+# For TickManager
+accessible field net/minecraft/server/ServerTickRateManager remainingSprintTicks J
diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/SimulatorCursor.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/SimulatorCursor.java
index 73bbfcd7..72178e23 100644
--- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/SimulatorCursor.java
+++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/SimulatorCursor.java
@@ -55,19 +55,11 @@ import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryDragEvent;
-import org.bukkit.event.player.PlayerDropItemEvent;
-import org.bukkit.event.player.PlayerItemHeldEvent;
-import org.bukkit.event.player.PlayerJoinEvent;
-import org.bukkit.event.player.PlayerQuitEvent;
-import org.bukkit.event.player.PlayerToggleSneakEvent;
+import org.bukkit.event.player.*;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
+import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/blastresistance/BlastResistanceCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/blastresistance/BlastResistanceCommand.java
index 7c9430a7..7afb0e5f 100644
--- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/blastresistance/BlastResistanceCommand.java
+++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/blastresistance/BlastResistanceCommand.java
@@ -32,7 +32,6 @@ import de.steamwar.linkage.Linked;
import org.bukkit.Material;
import org.bukkit.entity.Player;
-import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
@Linked
diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/TechHiderCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/TechHiderCommand.java
index 4744e41d..2f401a59 100644
--- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/TechHiderCommand.java
+++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/TechHiderCommand.java
@@ -31,6 +31,7 @@ import de.steamwar.command.TypeMapper;
import de.steamwar.core.CraftbukkitWrapper;
import de.steamwar.linkage.Linked;
import de.steamwar.linkage.LinkedInstance;
+import de.steamwar.techhider.legacy.TechHider;
import net.md_5.bungee.api.ChatMessageType;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
@@ -38,7 +39,6 @@ import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
-import de.steamwar.techhider.legacy.TechHider;
import java.util.*;
import java.util.stream.Collectors;
diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/Trace.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/Trace.java
index 95107761..8eb49d79 100644
--- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/Trace.java
+++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/Trace.java
@@ -25,7 +25,6 @@ import de.steamwar.bausystem.features.tracer.rendering.TraceEntity;
import de.steamwar.bausystem.features.tracer.rendering.ViewFlag;
import de.steamwar.bausystem.region.Region;
import de.steamwar.entity.REntity;
-import de.steamwar.entity.REntityAction;
import de.steamwar.entity.REntityServer;
import lombok.Getter;
import lombok.Setter;
diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/BindCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/BindCommand.java
index bde7967e..5db75a81 100644
--- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/BindCommand.java
+++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/BindCommand.java
@@ -38,6 +38,7 @@ import org.bukkit.Bukkit;
import org.bukkit.NamespacedKey;
import org.bukkit.command.CommandMap;
import org.bukkit.command.CommandSender;
+import org.bukkit.craftbukkit.CraftServer;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
@@ -47,7 +48,6 @@ import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataType;
-import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
@@ -73,19 +73,7 @@ public class BindCommand extends SWCommand implements Listener {
}
}
- private static final CommandMap commandMap;
-
- static {
- Field knownCommandsField;
- try {
- knownCommandsField = Bukkit.getServer().getClass().getDeclaredField("commandMap");
- knownCommandsField.setAccessible(true);
- commandMap = (CommandMap) knownCommandsField.get(Bukkit.getServer());
- } catch (IllegalAccessException | NoSuchFieldException var2) {
- Bukkit.shutdown();
- throw new SecurityException("Oh shit. Commands cannot be registered.", var2);
- }
- }
+ private static final CommandMap commandMap = ((CraftServer) Bukkit.getServer()).getCommandMap();
private static final NamespacedKey KEY = SWUtils.getNamespaceKey("command");
diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/NoClipCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/NoClipCommand.java
index 0b10e163..19bffd3f 100644
--- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/NoClipCommand.java
+++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/NoClipCommand.java
@@ -21,7 +21,6 @@ package de.steamwar.bausystem.features.util;
import com.comphenix.tinyprotocol.TinyProtocol;
import com.mojang.authlib.GameProfile;
-import de.steamwar.Reflection;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.features.tpslimit.TPSUtils;
import de.steamwar.bausystem.utils.BauMemberUpdateEvent;
@@ -30,7 +29,6 @@ import de.steamwar.core.ProtocolWrapper;
import de.steamwar.core.SWPlayer;
import de.steamwar.linkage.Linked;
import net.minecraft.network.protocol.game.*;
-import net.minecraft.server.level.ServerPlayerGameMode;
import net.minecraft.world.entity.player.Abilities;
import net.minecraft.world.level.GameType;
import org.bukkit.Bukkit;
@@ -103,10 +101,8 @@ public class NoClipCommand extends SWCommand implements Listener {
TinyProtocol.instance.addFilter(ServerboundSetCreativeModeSlotPacket.class, third);
}
- private static final Reflection.Field playerGameMode = Reflection.getField(ServerPlayerGameMode.class, GameType.class, 0);
-
private void setInternalGameMode(Player player, GameMode gameMode) {
- playerGameMode.set(((CraftPlayer) player).getHandle().gameMode, GameType.byId(gameMode.getValue()));
+ ((CraftPlayer) player).getHandle().gameMode.gameModeForPlayer = GameType.byId(gameMode.getValue());
}
@Register(help = true)
diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/BauMemberUpdate.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/BauMemberUpdate.java
index ed5b068b..d3416e08 100644
--- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/BauMemberUpdate.java
+++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/BauMemberUpdate.java
@@ -36,8 +36,6 @@ import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.PlayerDeathEvent;
-import org.bukkit.event.inventory.ClickType;
-import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.PlayerItemConsumeEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/SpectatorListener.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/SpectatorListener.java
index 92a5c59e..645600cd 100644
--- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/SpectatorListener.java
+++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/SpectatorListener.java
@@ -25,6 +25,7 @@ import de.steamwar.bausystem.config.BauServer;
import de.steamwar.bausystem.utils.BauMemberUpdateEvent;
import de.steamwar.linkage.Linked;
import de.steamwar.sql.BauweltMember;
+import de.steamwar.techhider.legacy.TechHider;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
@@ -39,7 +40,6 @@ import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.EntityPickupItemEvent;
import org.bukkit.event.player.*;
import org.bukkit.util.Vector;
-import de.steamwar.techhider.legacy.TechHider;
import java.util.HashSet;
import java.util.Set;
diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/xray/XrayCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/xray/XrayCommand.java
index 55bcbeeb..aa90081c 100644
--- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/xray/XrayCommand.java
+++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/xray/XrayCommand.java
@@ -28,6 +28,7 @@ import de.steamwar.command.SWCommand;
import de.steamwar.core.CraftbukkitWrapper;
import de.steamwar.linkage.Linked;
import de.steamwar.linkage.LinkedInstance;
+import de.steamwar.techhider.legacy.TechHider;
import net.md_5.bungee.api.ChatMessageType;
import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket;
import net.minecraft.server.level.ServerPlayer;
@@ -40,7 +41,6 @@ import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerQuitEvent;
-import de.steamwar.techhider.legacy.TechHider;
import java.util.*;
import java.util.function.BiFunction;
diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/PlaceItemUtils.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/PlaceItemUtils.java
index c4ab9a54..71f42cbb 100644
--- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/PlaceItemUtils.java
+++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/PlaceItemUtils.java
@@ -19,7 +19,6 @@
package de.steamwar.bausystem.utils;
-import de.steamwar.Reflection;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.experimental.UtilityClass;
@@ -86,9 +85,6 @@ public class PlaceItemUtils {
.collect(Collectors.toSet());
}
- private static final Reflection.Field> positionAccessor = Reflection.getField(CraftBlockState.class, BlockPos.class, 0);
- private static final Reflection.Field> worldAccessor = Reflection.getField(CraftBlockState.class, CraftWorld.class, 0);
-
/**
* Attempt to place an {@link ItemStack} the {@link Player} is holding against a {@link Block} inside the World.
* This can be easily used inside the {@link org.bukkit.event.player.PlayerInteractEvent} to mimik placing a
@@ -288,8 +284,9 @@ public class PlaceItemUtils {
} else {
// If a BlockState is present set the Position and World to the Block you want to place
Location blockLocation = block.getLocation();
- positionAccessor.set(blockState, new BlockPos(blockLocation.getBlockX(), blockLocation.getBlockY(), blockLocation.getBlockZ()));
- worldAccessor.set(blockState, blockLocation.getWorld());
+ CraftBlockState craftBlockState = (CraftBlockState) blockState;
+ craftBlockState.position = new BlockPos(blockLocation.getBlockX(), blockLocation.getBlockY(), blockLocation.getBlockZ());
+ craftBlockState.world = (CraftWorld) blockLocation.getWorld();
}
if (blockData.getMaterial().isSolid()) {
diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/TickManager.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/TickManager.java
index 59d12df2..613b3ea4 100644
--- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/TickManager.java
+++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/TickManager.java
@@ -20,7 +20,6 @@
package de.steamwar.bausystem.utils;
import com.comphenix.tinyprotocol.TinyProtocol;
-import de.steamwar.Reflection;
import de.steamwar.bausystem.BauSystem;
import net.minecraft.network.protocol.game.ClientboundTickingStatePacket;
import net.minecraft.server.MinecraftServer;
@@ -33,7 +32,6 @@ public class TickManager implements Listener {
public static final TickManager impl = new TickManager();
private static final ServerTickRateManager manager = MinecraftServer.getServer().tickRateManager();
- private static final Reflection.Field remainingSprintTicks = Reflection.getField(ServerTickRateManager.class, long.class, 0);
private boolean blockTpsPacket = true;
private int totalSteps;
@@ -121,7 +119,7 @@ public class TickManager implements Listener {
public long getRemainingTicks() {
if (isSprinting()) {
- return remainingSprintTicks.get(manager);
+ return manager.remainingSprintTicks;
} else {
return manager.frozenTicksToRun();
}
diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/WorldEditUtils.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/WorldEditUtils.java
index 0bab1548..8c493707 100644
--- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/WorldEditUtils.java
+++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/WorldEditUtils.java
@@ -19,6 +19,7 @@
package de.steamwar.bausystem.utils;
+import com.fastasyncworldedit.core.regions.selector.PolyhedralRegionSelector;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.IncompleteRegionException;
import com.sk89q.worldedit.LocalSession;
@@ -32,16 +33,19 @@ import com.sk89q.worldedit.internal.registry.InputParser;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.regions.RegionSelector;
+import com.sk89q.worldedit.regions.selector.*;
import com.sk89q.worldedit.regions.selector.limit.SelectorLimits;
-import de.steamwar.Reflection;
+import com.sk89q.worldedit.world.World;
import de.steamwar.bausystem.shared.Pair;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import org.bukkit.Location;
-import org.bukkit.World;
import org.bukkit.entity.Player;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
import java.util.stream.Collectors;
@UtilityClass
@@ -91,17 +95,36 @@ public class WorldEditUtils {
.getRegionSelector(BukkitAdapter.adapt(player.getWorld()));
return new Pair<>(regionSelector.getClass(), regionSelector.getVertices()
.stream()
- .map(blockVector3 -> blockVector3 == null ? null : adapt(player.getWorld(), blockVector3))
+ .map(blockVector3 -> {
+ if (blockVector3 == null) {
+ return null;
+ } else {
+ return BukkitAdapter.adapt(player.getWorld(), blockVector3);
+ }
+ })
.collect(Collectors.toList()));
}
+ private static final Map, Function> constructors = new HashMap<>();
+ static {
+ constructors.put(CuboidRegionSelector.class, CuboidRegionSelector::new);
+ constructors.put(ExtendingCuboidRegionSelector.class, ExtendingCuboidRegionSelector::new);
+ constructors.put(Polygonal2DRegionSelector.class, Polygonal2DRegionSelector::new);
+ constructors.put(EllipsoidRegionSelector.class, EllipsoidRegionSelector::new);
+ constructors.put(SphereRegionSelector.class, SphereRegionSelector::new);
+ constructors.put(CylinderRegionSelector.class, CylinderRegionSelector::new);
+ constructors.put(ConvexPolyhedralRegionSelector.class, ConvexPolyhedralRegionSelector::new);
+ constructors.put(PolyhedralRegionSelector.class, PolyhedralRegionSelector::new);
+ }
+
public void setVertices(Player player, Class extends RegionSelector> clazz, List vertices) {
LocalSession localSession = WorldEdit.getInstance()
.getSessionManager()
.get(BukkitAdapter.adapt(player));
- Reflection.Constructor constructorInvoker = Reflection.getConstructor(clazz, com.sk89q.worldedit.world.World.class);
- RegionSelector regionSelector = (RegionSelector) constructorInvoker.invoke(BukkitAdapter.adapt(player.getWorld()));
+ Function constructor = constructors.get(clazz);
+ if (constructor == null) return;
+ RegionSelector regionSelector = constructor.apply(BukkitAdapter.adapt(player.getWorld()));
localSession.setRegionSelector(BukkitAdapter.adapt(player.getWorld()), regionSelector);
if (vertices.isEmpty()) return;
@@ -127,13 +150,9 @@ public class WorldEditUtils {
try {
BlockVector3 min = regionSelector.getRegion().getMinimumPoint();
BlockVector3 max = regionSelector.getRegion().getMaximumPoint();
- return new Pair<>(adapt(player.getWorld(), min), adapt(player.getWorld(), max));
+ return new Pair<>(BukkitAdapter.adapt(player.getWorld(), min), BukkitAdapter.adapt(player.getWorld(), max));
} catch (IncompleteRegionException e) {
return null;
}
}
-
- private Location adapt(World world, BlockVector3 blockVector3) {
- return new Location(world, blockVector3.getBlockX(), blockVector3.getBlockY(), blockVector3.getBlockZ());
- }
}
diff --git a/BauSystem/BauSystem_Main/src/plugin.yml b/BauSystem/BauSystem_Main/src/plugin.yml
index 71b08d95..bbc550bc 100644
--- a/BauSystem/BauSystem_Main/src/plugin.yml
+++ b/BauSystem/BauSystem_Main/src/plugin.yml
@@ -4,7 +4,7 @@ version: "2.0"
depend: [ WorldEdit, SpigotCore ]
load: POSTWORLD
main: de.steamwar.bausystem.BauSystem
-api-version: "1.13"
+api-version: "1.21"
website: "https://steamwar.de"
description: "So unseriös wie wir sind: BauSystem nur besser."
diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java
index d3339975..06ceac41 100644
--- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java
+++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java
@@ -32,7 +32,10 @@ import de.steamwar.fightsystem.record.GlobalRecorder;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.OneShotStateDependent;
import de.steamwar.fightsystem.states.StateDependentListener;
-import de.steamwar.fightsystem.utils.*;
+import de.steamwar.fightsystem.utils.FightUI;
+import de.steamwar.fightsystem.utils.HullHider;
+import de.steamwar.fightsystem.utils.SWSound;
+import de.steamwar.fightsystem.utils.TechHiderWrapper;
import de.steamwar.linkage.AbstractLinker;
import de.steamwar.linkage.SpigotLinker;
import de.steamwar.message.Message;
diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/EnternCountdown.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/EnternCountdown.java
index 87a45b4a..e5a62ac6 100644
--- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/EnternCountdown.java
+++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/EnternCountdown.java
@@ -27,12 +27,9 @@ import de.steamwar.fightsystem.fight.FightPlayer;
import de.steamwar.fightsystem.utils.Message;
import de.steamwar.fightsystem.utils.Region;
import de.steamwar.fightsystem.utils.SWSound;
-import de.steamwar.techhider.ProtocolUtils;
import net.md_5.bungee.api.ChatMessageType;
import org.bukkit.Bukkit;
-import java.util.List;
-
public class EnternCountdown extends Countdown {
private static int calcTime(FightPlayer fp, Countdown countdown) {
@@ -47,7 +44,6 @@ public class EnternCountdown extends Countdown {
}
private final FightPlayer fightPlayer;
- private List chunkPos;
public EnternCountdown(FightPlayer fp, Countdown countdown) {
super(calcTime(fp, countdown), new Message("ENTERN_COUNTDOWN"), SWSound.BLOCK_NOTE_PLING, false);
diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightTeam.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightTeam.java
index a8cddb3a..0119bf40 100644
--- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightTeam.java
+++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightTeam.java
@@ -35,7 +35,9 @@ import de.steamwar.fightsystem.listener.TeamArea;
import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.OneShotStateDependent;
import de.steamwar.fightsystem.states.StateDependent;
-import de.steamwar.fightsystem.utils.*;
+import de.steamwar.fightsystem.utils.FightUI;
+import de.steamwar.fightsystem.utils.ItemBuilder;
+import de.steamwar.fightsystem.utils.Region;
import de.steamwar.fightsystem.winconditions.Wincondition;
import de.steamwar.fightsystem.winconditions.Winconditions;
import de.steamwar.inventory.SWItem;
diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/InFightInventory.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/InFightInventory.java
index 8d7d339c..c49b46c6 100644
--- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/InFightInventory.java
+++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/InFightInventory.java
@@ -25,7 +25,6 @@ import de.steamwar.fightsystem.states.FightState;
import de.steamwar.fightsystem.states.StateDependentListener;
import de.steamwar.linkage.Linked;
import org.bukkit.Material;
-import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Recording.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Recording.java
index 0187d089..ecd333ec 100644
--- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Recording.java
+++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Recording.java
@@ -20,7 +20,6 @@
package de.steamwar.fightsystem.listener;
import com.comphenix.tinyprotocol.TinyProtocol;
-import de.steamwar.Reflection;
import de.steamwar.fightsystem.ArenaMode;
import de.steamwar.fightsystem.Config;
import de.steamwar.fightsystem.FightSystem;
@@ -37,7 +36,6 @@ import de.steamwar.fightsystem.states.StateDependentListener;
import de.steamwar.fightsystem.states.StateDependentTask;
import de.steamwar.fightsystem.utils.SWSound;
import de.steamwar.linkage.Linked;
-import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket;
import net.minecraft.network.protocol.game.ServerboundUseItemPacket;
import net.minecraft.world.InteractionHand;
@@ -115,18 +113,18 @@ public class Recording implements Listener {
}.register();
new StateDependent(ArenaMode.AntiReplay, FightState.Ingame) {
private final BiFunction place = Recording.this::blockPlace;
- private final BiFunction dig = Recording.this::blockDig;
+ private final BiFunction dig = Recording.this::blockDig;
@Override
public void enable() {
TinyProtocol.instance.addFilter(ServerboundUseItemPacket.class, place);
- TinyProtocol.instance.addFilter(blockDigPacket, dig);
+ TinyProtocol.instance.addFilter(ServerboundPlayerActionPacket.class, dig);
}
@Override
public void disable() {
TinyProtocol.instance.removeFilter(ServerboundUseItemPacket.class, place);
- TinyProtocol.instance.removeFilter(blockDigPacket, dig);
+ TinyProtocol.instance.removeFilter(ServerboundPlayerActionPacket.class, dig);
}
}.register();
new StateDependentTask(ArenaMode.AntiReplay, FightState.All, () -> {
@@ -143,13 +141,8 @@ public class Recording implements Listener {
GlobalRecorder.getInstance().entitySpeed(entity);
}
- private static final Class extends Packet>> blockDigPacket = ServerboundPlayerActionPacket.class;
- private static final Class> playerDigType = blockDigPacket.getDeclaredClasses()[0];
- private static final Reflection.Field> blockDigType = Reflection.getField(blockDigPacket, playerDigType, 0);
- private static final Object releaseUseItem = playerDigType.getEnumConstants()[5];
-
- private Object blockDig(Player p, Object packet) {
- if (!isNotSent(p) && blockDigType.get(packet) == releaseUseItem) {
+ private Object blockDig(Player p, ServerboundPlayerActionPacket packet) {
+ if (!isNotSent(p) && packet.getAction() == ServerboundPlayerActionPacket.Action.RELEASE_USE_ITEM) {
GlobalRecorder.getInstance().bowSpan(p, false, false);
}
return packet;
diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/PacketProcessor.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/PacketProcessor.java
index f4f6983a..80fcf091 100644
--- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/PacketProcessor.java
+++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/PacketProcessor.java
@@ -34,7 +34,9 @@ import de.steamwar.fightsystem.fight.FightWorld;
import de.steamwar.fightsystem.fight.FreezeWorld;
import de.steamwar.fightsystem.listener.FightScoreboard;
import de.steamwar.fightsystem.states.FightState;
-import de.steamwar.fightsystem.utils.*;
+import de.steamwar.fightsystem.utils.FightUI;
+import de.steamwar.fightsystem.utils.Message;
+import de.steamwar.fightsystem.utils.TechHiderWrapper;
import de.steamwar.sql.SchematicNode;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.Team;
diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/TechHiderWrapper.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/TechHiderWrapper.java
index 3fc4a364..7c4c9d77 100644
--- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/TechHiderWrapper.java
+++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/TechHiderWrapper.java
@@ -19,7 +19,6 @@
package de.steamwar.fightsystem.utils;
-import de.steamwar.Reflection;
import de.steamwar.core.CraftbukkitWrapper;
import de.steamwar.fightsystem.Config;
import de.steamwar.fightsystem.events.BoardingEvent;
@@ -51,7 +50,7 @@ import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
import java.util.Collection;
-import java.util.Optional;
+import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@@ -82,18 +81,13 @@ public class TechHiderWrapper extends StateDependent implements Listener {
.map(CraftMagicNumbers::getBlock)
.collect(Collectors.toUnmodifiableSet());
- Object blockEntityType;
- try {
- blockEntityType = BuiltInRegistries.class.getDeclaredField("BLOCK_ENTITY_TYPE").get(null);
- } catch (Exception e) {
- throw new IllegalStateException(e);
- }
- Reflection.Method method = Reflection.getTypedMethod(Reflection.getClass("net.minecraft.core.Registry"), "get", Optional.class, ResourceLocation.class);
Set> blockEntityTypeToObfuscate = Config.GameModeConfig.Techhider.HiddenBlockEntities.stream()
- .map((id) -> {
+ .map(id -> {
ResourceLocation loc = ResourceLocation.parse(id);
- return ((Optional>>) method.invoke(blockEntityType, loc)).get().value();
+ return BuiltInRegistries.BLOCK_ENTITY_TYPE.get(loc).orElse(null);
})
+ .filter(Objects::nonNull)
+ .map(Holder.Reference::value)
.collect(Collectors.toUnmodifiableSet());
new TechHider(CraftMagicNumbers.getBlock(Config.GameModeConfig.Techhider.ObfuscateWith), new AccessPrivilegeProvider() {
diff --git a/LobbySystem/build.gradle.kts b/LobbySystem/build.gradle.kts
index a97d8394..7dd7d27f 100644
--- a/LobbySystem/build.gradle.kts
+++ b/LobbySystem/build.gradle.kts
@@ -19,6 +19,7 @@
plugins {
steamwar.java
+ widener
}
dependencies {
@@ -32,6 +33,11 @@ dependencies {
compileOnly(libs.fawe)
}
+widener {
+ fromCatalog(libs.nms)
+ fromCatalog(libs.paperapi)
+}
+
tasks.register("DevLobby20") {
group = "run"
description = "Run a 1.20 Dev Lobby"
diff --git a/LobbySystem/src/de/steamwar/lobby/map/ColorInit.java b/LobbySystem/src/de/steamwar/lobby/map/ColorInit.java
index ed5e0457..d5ffca23 100644
--- a/LobbySystem/src/de/steamwar/lobby/map/ColorInit.java
+++ b/LobbySystem/src/de/steamwar/lobby/map/ColorInit.java
@@ -42,7 +42,7 @@ public class ColorInit {
if (inputStream == null) {
colors = new byte[256 * 256 * 256];
for (int i = 0; i < colors.length; i++) {
- colors[i] = MapPalette.matchColor(new Color(i));
+ colors[i] = matchColor(new Color(i));
}
} else {
try {
@@ -57,4 +57,26 @@ public class ColorInit {
}
System.out.println("[ColorInit] Initialization took " + (System.currentTimeMillis() - time) + "ms");
}
+
+ public static byte matchColor(Color color) {
+ if (color.getAlpha() < 128) return 0;
+
+ if (MapPalette.mapColorCache != null && MapPalette.mapColorCache.isCached()) {
+ return MapPalette.mapColorCache.matchColor(color);
+ }
+
+ int index = 0;
+ double best = -1;
+
+ for (int i = 4; i < MapPalette.colors.length; i++) {
+ double distance = MapPalette.getDistance(color, MapPalette.colors[i]);
+ if (distance < best || best == -1) {
+ best = distance;
+ index = i;
+ }
+ }
+
+ // Minecraft has 248 colors, some of which have negative byte representations
+ return (byte) (index < 128 ? index : -129 + (index - 127));
+ }
}
diff --git a/LobbySystem/src/de/steamwar/lobby/map/CustomMap.java b/LobbySystem/src/de/steamwar/lobby/map/CustomMap.java
index 5c140ae9..ee0ebb5a 100644
--- a/LobbySystem/src/de/steamwar/lobby/map/CustomMap.java
+++ b/LobbySystem/src/de/steamwar/lobby/map/CustomMap.java
@@ -254,7 +254,8 @@ public class CustomMap implements Listener {
int green = pixels[i2];
int i3 = (y * width + x) * numBands + 2;
int blue = pixels[i3];
- Color nearest = MapPalette.getColor(ColorInit.getColorByte(red, green, blue));
+ int colorIndex = ColorInit.getColorByte(red, green, blue);
+ Color nearest = MapPalette.colors[colorIndex >= 0 ? colorIndex : colorIndex + 256];
pixels[(y * width + x) * numBands] = nearest.getRed();
pixels[i2] = nearest.getGreen();
diff --git a/LobbySystem/src/lobby.accesswidener b/LobbySystem/src/lobby.accesswidener
new file mode 100644
index 00000000..75a3fca5
--- /dev/null
+++ b/LobbySystem/src/lobby.accesswidener
@@ -0,0 +1,6 @@
+accessWidener v2 named
+
+# For CustomMap and ColorInit
+accessible field org/bukkit/map/MapPalette colors [Ljava/awt/Color;
+accessible method org/bukkit/map/MapPalette getDistance (Ljava/awt/Color;Ljava/awt/Color;)D
+accessible field org/bukkit/map/MapPalette mapColorCache Lorg/bukkit/map/MapPalette$MapColorCache;
\ No newline at end of file
diff --git a/MissileWars/src/de/steamwar/misslewars/slowmo/SlowMoRunner.java b/MissileWars/src/de/steamwar/misslewars/slowmo/SlowMoRunner.java
index 0d0363f0..84df082c 100644
--- a/MissileWars/src/de/steamwar/misslewars/slowmo/SlowMoRunner.java
+++ b/MissileWars/src/de/steamwar/misslewars/slowmo/SlowMoRunner.java
@@ -20,9 +20,12 @@
package de.steamwar.misslewars.slowmo;
import de.steamwar.misslewars.MissileWars;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.world.TickRateManager;
import org.bukkit.Bukkit;
public class SlowMoRunner {
+ private static TickRateManager tickRateManager = MinecraftServer.getServer().tickRateManager();
private static long currentTime = 0;
private static long current = 0;
@@ -40,14 +43,14 @@ public class SlowMoRunner {
if (currentTime > 0) {
current += 1;
if (current % 5 == 0) {
- SlowMoUtils.unfreeze();
+ tickRateManager.setFrozen(false);
current = 0;
} else {
- SlowMoUtils.freeze();
+ tickRateManager.setFrozen(true);
}
currentTime--;
} else {
- SlowMoUtils.unfreeze();
+ tickRateManager.setFrozen(false);
}
}, 0, 1);
}
diff --git a/MissileWars/src/de/steamwar/misslewars/slowmo/SlowMoUtils.java b/MissileWars/src/de/steamwar/misslewars/slowmo/SlowMoUtils.java
deleted file mode 100644
index c5e01cc4..00000000
--- a/MissileWars/src/de/steamwar/misslewars/slowmo/SlowMoUtils.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * This file is a part of the SteamWar software.
- *
- * Copyright (C) 2025 SteamWar.de-Serverteam
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-package de.steamwar.misslewars.slowmo;
-
-import net.minecraft.server.level.ServerLevel;
-import org.bukkit.Bukkit;
-import org.bukkit.World;
-import org.bukkit.craftbukkit.CraftWorld;
-
-import java.lang.reflect.Field;
-
-public class SlowMoUtils {
-
- private static final Field field;
- public static final boolean freezeEnabled;
-
- private static boolean frozen = false;
-
- private static final World world;
-
- static {
- Field temp;
- try {
- temp = ServerLevel.class.getField("freezed");
- } catch (NoSuchFieldException e) {
- temp = null;
- }
- field = temp;
- if (field != null) field.setAccessible(true);
- freezeEnabled = field != null;
- world = Bukkit.getWorlds().get(0);
- }
-
- public static void freeze() {
- setFreeze(world, true);
- }
-
- public static void unfreeze() {
- setFreeze(world, false);
- }
-
- public static boolean frozen() {
- return freezeEnabled && frozen;
- }
-
- private static void setFreeze(World world, boolean state) {
- if (freezeEnabled) {
- if (frozen == state) return;
- try {
- field.set(((CraftWorld) world).getHandle(), state);
- frozen = state;
- } catch (IllegalAccessException e) {
- // Ignored;
- }
- }
- }
-}
diff --git a/SpigotCore/SpigotCore_Main/build.gradle.kts b/SpigotCore/SpigotCore_Main/build.gradle.kts
index a5e3227d..322b1046 100644
--- a/SpigotCore/SpigotCore_Main/build.gradle.kts
+++ b/SpigotCore/SpigotCore_Main/build.gradle.kts
@@ -19,6 +19,7 @@
plugins {
steamwar.java
+ widener
}
tasks.compileJava {
@@ -61,3 +62,7 @@ dependencies {
implementation(libs.anvilgui)
}
+
+widener {
+ fromCatalog(libs.nms)
+}
diff --git a/SpigotCore/SpigotCore_Main/src/com/comphenix/tinyprotocol/TinyProtocol.java b/SpigotCore/SpigotCore_Main/src/com/comphenix/tinyprotocol/TinyProtocol.java
index f7df16c1..5391f59b 100644
--- a/SpigotCore/SpigotCore_Main/src/com/comphenix/tinyprotocol/TinyProtocol.java
+++ b/SpigotCore/SpigotCore_Main/src/com/comphenix/tinyprotocol/TinyProtocol.java
@@ -1,7 +1,6 @@
package com.comphenix.tinyprotocol;
import com.google.common.collect.MapMaker;
-import de.steamwar.Reflection;
import de.steamwar.core.CRIUWakeupEvent;
import de.steamwar.core.Core;
import io.netty.channel.*;
@@ -180,22 +179,12 @@ public class TinyProtocol {
networkManagers = serverConnection.getConnections();
// We need to synchronize against this list
createServerChannelHandler();
+ for (ChannelFuture item : serverConnection.channels) {
+ // Channel future that contains the server connection
+ Channel serverChannel = item.channel();
- // Find the correct list, or implicitly throw an exception
- boolean looking = true;
- for (int i = 0; looking; i++) {
- List