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/.gitignore b/.gitignore
index dd7af7f6..ba7d8990 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,6 +15,11 @@ bin/
.vscode
.settings
+# Language Server
+**/.project
+**/.factorypath
+**/.classpath
+
# Other
lib
/WebsiteBackend/data
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 6d594c79..c6316f7d 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 {
@@ -48,3 +49,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/region/TestblockCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/region/TestblockCommand.java
index 1644aaea..bce22006 100644
--- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/region/TestblockCommand.java
+++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/region/TestblockCommand.java
@@ -185,17 +185,16 @@ public class TestblockCommand extends SWCommand {
return new TypeMapper() {
@Override
public List tabCompletes(CommandSender commandSender, PreviousArguments previousArguments, String s) {
- List stringList = new ArrayList<>(SchematicNode.getNodeTabcomplete(SteamwarUser.get(((Player) commandSender).getUniqueId()), s));
+ List stringList = new ArrayList<>();
stringList.addAll(SchematicNode.getNodeTabcomplete(SteamwarUser.byId(0), s));
+ stringList.addAll(SchematicNode.getNodeTabcomplete(SteamwarUser.get(((Player) commandSender).getUniqueId()), s));
return stringList;
}
@Override
public SchematicNode map(CommandSender commandSender, PreviousArguments previousArguments, String s) {
- SchematicNode node = SchematicNode.getNodeFromPath(SteamwarUser.get(((Player) commandSender).getUniqueId()), s);
- if (node == null) {
- node = SchematicNode.getNodeFromPath(SteamwarUser.byId(0), s);
- }
+ SchematicNode node = SchematicNode.getNodeFromPath(SteamwarUser.byId(0), s);
+ if (node == null) node = SchematicNode.getNodeFromPath(SteamwarUser.get(((Player) commandSender).getUniqueId()), s);
return node;
}
};
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/tracer/rendering/TraceEntity.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/rendering/TraceEntity.java
index e2be5eb9..160e4d58 100644
--- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/rendering/TraceEntity.java
+++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/rendering/TraceEntity.java
@@ -48,9 +48,9 @@ import static de.steamwar.bausystem.features.util.TNTClickListener.TNT_CLICK_DET
*/
public class TraceEntity extends RBlockDisplay {
- private static final float TNT_VISUAL_SCALE = 0.98F;
- private static final float TNT_VISUAL_OFFSET = -TNT_VISUAL_SCALE / 2.0F;
- private static final Transformation TNT_VISUAL_TRANSFORM = new Transformation(
+ public static final float TNT_VISUAL_SCALE = 0.98F;
+ public static final float TNT_VISUAL_OFFSET = -TNT_VISUAL_SCALE / 2.0F;
+ public static final Transformation TNT_VISUAL_TRANSFORM = new Transformation(
new Vector3f(TNT_VISUAL_OFFSET, 0.0F, TNT_VISUAL_OFFSET),
new Quaternionf(0, 0, 0, 1),
new Vector3f(TNT_VISUAL_SCALE, TNT_VISUAL_SCALE, TNT_VISUAL_SCALE),
diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/rendering/ViewFlag.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/rendering/ViewFlag.java
index 3938d88e..da08fecd 100644
--- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/rendering/ViewFlag.java
+++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/rendering/ViewFlag.java
@@ -131,6 +131,7 @@ public abstract class ViewFlag {
if (yLocation.distanceSquared(representative.getLocation()) >= 1.0 / 256.0 && yLocation.distanceSquared(previous.getLocation()) >= 1.0 / 256.0) {
RBlockDisplay y = new RBlockDisplay(server, yLocation);
y.setBlock(Material.WHITE_STAINED_GLASS.createBlockData());
+ y.setTransform(TraceEntity.TNT_VISUAL_TRANSFORM);
}
Location secoundLocation;
@@ -143,6 +144,7 @@ public abstract class ViewFlag {
if (secoundLocation.distanceSquared(representative.getLocation()) >= 1.0 / 256.0 && secoundLocation.distanceSquared(previous.getLocation()) >= 1.0 / 256.0) {
RBlockDisplay second = new RBlockDisplay(server, secoundLocation);
second.setBlock(Material.WHITE_STAINED_GLASS.createBlockData());
+ second.setTransform(TraceEntity.TNT_VISUAL_TRANSFORM);
}
}
}
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..dd143f0a 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)
@@ -143,6 +139,7 @@ public class NoClipCommand extends SWCommand implements Listener {
@EventHandler(ignoreCancelled = true)
public void onBlock(BlockCanBuildEvent event) {
+ if (event.getPlayer() == null) return;
if (SWPlayer.of(event.getPlayer()).hasComponent(NoClipData.class)) {
event.setBuildable(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/world/WorldEditListener.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/WorldEditListener.java
index 26943331..b0373595 100644
--- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/WorldEditListener.java
+++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/WorldEditListener.java
@@ -57,7 +57,7 @@ public class WorldEditListener implements Listener {
private static final Set commands = new HashSet<>();
private static final Set commandExclusions = new HashSet<>();
- private static final String[] shortcutCommands = {"//1", "//2", "//90", "//-90", "//180", "//p", "//c", "//flopy", "//floppy", "//flopyp", "//floppyp", "//u", "//r"};
+ private static final String[] shortcutCommands = {"//1", "//2", "//90", "//-90", "//180", "//p", "//c", "//flopy", "//floppy", "//flopyp", "//floppyp", "//u", "//r", "//download", "/download"};
public static boolean isWorldEditCommand(String command) {
for (String shortcut : shortcutCommands) {
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/CLI/build.gradle.kts b/CLI/build.gradle.kts
index f27d16b8..9629fc04 100644
--- a/CLI/build.gradle.kts
+++ b/CLI/build.gradle.kts
@@ -18,6 +18,7 @@ dependencies {
implementation("com.github.ajalt.clikt:clikt:5.0.3")
implementation("com.github.ajalt.mordant:mordant:3.0.2")
implementation(libs.logback)
+ implementation("org.yaml:snakeyaml:2.2")
implementation("org.mariadb.jdbc:mariadb-java-client:3.3.1")
implementation(libs.exposedCore)
diff --git a/CLI/src/commands/dev/DevCommand.kt b/CLI/src/commands/dev/DevCommand.kt
index 03b1ed2f..0cc75728 100644
--- a/CLI/src/commands/dev/DevCommand.kt
+++ b/CLI/src/commands/dev/DevCommand.kt
@@ -14,9 +14,11 @@ import com.github.ajalt.clikt.parameters.types.file
import com.github.ajalt.clikt.parameters.types.long
import com.github.ajalt.clikt.parameters.types.path
import com.sun.security.auth.module.UnixSystem
+import org.yaml.snakeyaml.Yaml
import java.io.File
import kotlin.io.path.absolute
import kotlin.io.path.absolutePathString
+import kotlin.random.Random
const val LOG4J_CONFIG = """
@@ -69,32 +71,42 @@ class DevCommand : CliktCommand("dev") {
override fun run() {
val args = mutableListOf()
- val serverDirectory = File(workingDir, server)
- val serverDir =
- if (serverDirectory.exists() && serverDirectory.isDirectory) serverDirectory else File(workingDir, server)
+ var serverDir = resolveServerDirectory(server)
if (isVelocity(server)) {
runServer(
args, jvmArgs, listOf(
jar?.absolutePath
- ?: File("/jar/Velocity.jar").absolutePath
+ ?: File("/jars/Velocity.jar").absolutePath
), serverDir
)
} else {
setLogConfig(args)
val version = findVersion(server)
?: throw CliktError("Unknown Server Version")
+ val gameModeTemplate = if (serverDir.isDirectory) null else loadGameModeTemplate(server)
+ if (gameModeTemplate != null) {
+ serverDir = gameModeTemplate.serverDir
+ args += "-Dconfig=$server.yml"
+ }
val worldFile = world?.absolute()?.toFile()
- ?: File(serverDir, "devtempworld")
- val jarFile = jar?.absolutePath
+ ?: File(workingDir, "devtempworld")
+ var jarFile = jar?.absolutePath
?: additionalVersions[server]?.let { supportedVersionJars[it] }
?: supportedVersionJars[version]
?: throw CliktError("Unknown Server Version")
+ if (gameModeTemplate != null) {
+ jarFile = if (gameModeTemplate.spigot) {
+ jarFile.replace("paper", "spigot")
+ } else {
+ jarFile.replace("spigot", "paper")
+ }
+ }
if (!worldFile.exists()) {
- val templateFile = File(serverDir, "Bauwelt")
+ val templateFile = gameModeTemplate?.worldTemplate ?: File(serverDir, "Bauwelt")
if (!templateFile.exists()) {
- throw CliktError("World Template not found!")
+ throw CliktError("Could not find world template: ${templateFile.absolutePath}")
}
templateFile.copyRecursively(worldFile)
}
@@ -123,6 +135,12 @@ class DevCommand : CliktCommand("dev") {
}
}
+ data class GameModeTemplate(
+ val serverDir: File,
+ val worldTemplate: File,
+ val spigot: Boolean
+ )
+
val jvmDefaultParams = arrayOf(
"-Xmx1G",
"-Xgc:excessiveGCratio=80",
@@ -152,8 +170,7 @@ class DevCommand : CliktCommand("dev") {
)
val additionalVersions = mapOf(
- "Tutorial" to 15,
- "Lobby" to 20
+ "Lobby" to 21
)
fun findVersion(server: String): Int? =
@@ -166,6 +183,41 @@ class DevCommand : CliktCommand("dev") {
fun isVelocity(server: String): Boolean =
server.endsWith("Velocity")
+ fun resolveServerDirectory(server: String): File {
+ val localServer = File(workingDir, server)
+ if (localServer.isDirectory) {
+ return localServer
+ }
+ return File("/servers", server)
+ }
+
+ fun loadGameModeTemplate(server: String): GameModeTemplate? {
+ val configFile = File("/configs/GameModes/$server.yml")
+ if (!configFile.exists()) {
+ throw CliktError("Server/GameMode not found")
+ }
+
+ val document = configFile.reader().use { reader ->
+ Yaml().load