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/AccessWidener/build.gradle.kts b/AccessWidener/build.gradle.kts new file mode 100644 index 00000000..a6632cf1 --- /dev/null +++ b/AccessWidener/build.gradle.kts @@ -0,0 +1,45 @@ +/* + * 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 . + */ + +plugins { + `java-library` + alias(libs.plugins.shadow) +} + +dependencies { + implementation("org.ow2.asm:asm:9.7") + implementation("org.ow2.asm:asm-commons:9.7") +} + +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. + *

+ * At JVM startup: java -javaagent:paper-access-widener-agent.jar -jar server.jar + *

+ * On attach the agent: + *

    + *
  1. Find all .jar files inside the plugins folder
  2. + *
  3. Scan all found jars for *.accesswidener resources
  4. + *
  5. Transform any class during loading
  6. + *
+ */ +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 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 5fb90333..a32389fb 100644 --- a/BauSystem/BauSystem_Main/build.gradle.kts +++ b/BauSystem/BauSystem_Main/build.gradle.kts @@ -18,6 +18,7 @@ */ plugins { + widener steamwar.kotlin } @@ -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.properties b/BauSystem/BauSystem_Main/src/BauSystem.properties index a045edf4..c5adfb70 100644 --- a/BauSystem/BauSystem_Main/src/BauSystem.properties +++ b/BauSystem/BauSystem_Main/src/BauSystem.properties @@ -834,6 +834,10 @@ SKIN_NO_REGION = §7You are not in a region with a changealbe skin SKIN_ALREADY_EXISTS = §cThis skin already exists like this SKIN_MESSAGE = §7Skin created SKIN_MESSAGE_HOVER = §eClick to copy for YoyoNow and send +# Blast Resistance +BLASTRESISTANCE_HELP = §8/§eblastresistance §8-§7 Calculate min/max and average blast resistance of current clipboard +BLASTRESISTANCE_NO_CLIPBOARD = §cYou currently do not have a clipboard to be used. +BLASTRESISTANCE_RESULT = §7BlastResistance §8>>§7 Min§8: §e{0}§7 Max§8: §e{1}§7 Avg§8: §e{2} # Panzern PANZERN_HELP = §8/§epanzern §8[§7Block§8] §8[§7Slab§8] §8- §7Armor your WorldEdit selection PANZERN_PREPARE1 = §71. Check, if barrels reach until border of armor. diff --git a/BauSystem/BauSystem_Main/src/BauSystem_de.properties b/BauSystem/BauSystem_Main/src/BauSystem_de.properties index dedc0599..9bee037c 100644 --- a/BauSystem/BauSystem_Main/src/BauSystem_de.properties +++ b/BauSystem/BauSystem_Main/src/BauSystem_de.properties @@ -772,6 +772,10 @@ SKIN_NO_REGION = §7Du steht in keiner Region, welche mit einem Skin versehen we SKIN_ALREADY_EXISTS = §cDieser Skin existiert in der Form bereits SKIN_MESSAGE = §7Skin erstellt SKIN_MESSAGE_HOVER = §eKlicken zum kopieren für YoyoNow und an diesen senden +# Blast Resistance +BLASTRESISTANCE_HELP = §8/§eblastresistance §8-§7 Minimal-, Maximal- und durchschnittliche Sprengfestigkeit des aktuellen Inhalts der Zwischenablage berechnen +BLASTRESISTANCE_NO_CLIPBOARD = §cDerzeit steht Ihnen keine Zwischenablage zur Verfügung. +BLASTRESISTANCE_RESULT = §7BlastResistance §8>>§7 Min§8: §e{0}§7 Max§8: §e{1}§7 Avg§8: §e{2} # Panzern PANZERN_HELP = §8/§epanzern §8[§7Block§8] §8[§7Slab§8] §8- §7Panzer deine WorldEdit Auswahl PANZERN_PREPARE1 = §71. Gucke nochmal nach, ob Läufe auch bis zur Panzergrenze führen. 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/SpigotCore/SpigotCore_Main/src/de/steamwar/core/FlatteningWrapper.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/experimental/ExperimentalCommand.java similarity index 60% rename from SpigotCore/SpigotCore_Main/src/de/steamwar/core/FlatteningWrapper.java rename to BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/experimental/ExperimentalCommand.java index 6d6d4d1b..a765a351 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/FlatteningWrapper.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/experimental/ExperimentalCommand.java @@ -1,7 +1,7 @@ /* * This file is a part of the SteamWar software. * - * Copyright (C) 2025 SteamWar.de-Serverteam + * 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 @@ -17,18 +17,15 @@ * along with this program. If not, see . */ -package de.steamwar.core; +package de.steamwar.bausystem.features.experimental; -import lombok.experimental.UtilityClass; -import net.minecraft.network.chat.MutableComponent; -import net.minecraft.network.chat.contents.PlainTextContents; +import de.steamwar.command.SWCommand; +import de.steamwar.linkage.Linked; -import java.util.Optional; +@Linked +public class ExperimentalCommand extends SWCommand { -@UtilityClass -public class FlatteningWrapper { - - public Object formatDisplayName(String displayName) { - return displayName != null ? Optional.of((Object) MutableComponent.create(PlainTextContents.create(displayName))) : Optional.empty(); + public ExperimentalCommand() { + super("experimental", "experiment"); } } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/experimental/redstone_engine/RedstoneEngine.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/experimental/redstone_engine/RedstoneEngine.java new file mode 100644 index 00000000..1247b667 --- /dev/null +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/experimental/redstone_engine/RedstoneEngine.java @@ -0,0 +1,135 @@ +/* + * 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.bausystem.features.experimental.redstone_engine; + +import de.steamwar.bausystem.features.experimental.ExperimentalCommand; +import de.steamwar.bausystem.region.Region; +import de.steamwar.bausystem.utils.ScoreboardElement; +import de.steamwar.command.AbstractSWCommand; +import de.steamwar.command.SWCommand; +import de.steamwar.linkage.Linked; +import io.papermc.paper.configuration.WorldConfiguration; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.title.TitlePart; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; + +import java.util.Collection; +import java.util.List; + +@AbstractSWCommand.PartOf(ExperimentalCommand.class) +@Linked +public class RedstoneEngine extends SWCommand implements Listener, ScoreboardElement { + + public RedstoneEngine() { + super(""); + } + + private WorldConfiguration.Misc getConfig() { + return ((CraftWorld) Bukkit.getWorlds().get(0)).getHandle().paperConfig().misc; + } + + @Register("redstone") + @Register("redstoneengine") + public void setRedstoneEngine(Player player, @StaticValue("alternate_current") String __, WorldConfiguration.Misc.AlternateCurrentUpdateOrder updateOrder) { + WorldConfiguration.Misc misc = getConfig(); + misc.redstoneImplementation = WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT; + misc.alternateCurrentUpdateOrder = updateOrder; + broadcastTitle(Bukkit.getOnlinePlayers()); + } + + @Register("redstone") + @Register("redstoneengine") + public void setRedstoneEngine(Player player, WorldConfiguration.Misc.RedstoneImplementation implementation) { + getConfig().redstoneImplementation = implementation; + broadcastTitle(Bukkit.getOnlinePlayers()); + } + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + WorldConfiguration.Misc misc = getConfig(); + if (misc.redstoneImplementation != WorldConfiguration.Misc.RedstoneImplementation.EIGENCRAFT) { + broadcastTitle(List.of(event.getPlayer())); + } + } + + private void broadcastTitle(Collection players) { + WorldConfiguration.Misc misc = getConfig(); + Component title = switch (misc.redstoneImplementation) { + case VANILLA -> Component.text("⚠").color(NamedTextColor.RED).append(Component.text(" Redstone: Vanilla ").color(NamedTextColor.WHITE)).append(Component.text("⚠").color(NamedTextColor.RED)); + case EIGENCRAFT -> Component.text("Redstone: Eigencraft"); + case ALTERNATE_CURRENT -> Component.text("⚠").color(NamedTextColor.RED).append(Component.text(" Redstone: AC ").color(NamedTextColor.WHITE)).append(Component.text("⚠").color(NamedTextColor.RED)); + }; + Component subtitle; + if (misc.redstoneImplementation != WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) { + subtitle = Component.text().build(); + } else { + subtitle = switch (misc.alternateCurrentUpdateOrder) { + case VERTICAL_FIRST_INWARD -> Component.text("Y first inwards"); // Y before XZ + case VERTICAL_FIRST_OUTWARD -> Component.text("Y first outwards"); // XZ before Y + case HORIZONTAL_FIRST_INWARD -> Component.text("XZ first inwards"); // Y before XZ + case HORIZONTAL_FIRST_OUTWARD -> Component.text("XZ first outwards"); // XZ before Y + }; + } + players.forEach(player -> { + player.sendTitlePart(TitlePart.TITLE, title); + player.sendTitlePart(TitlePart.SUBTITLE, subtitle); + }); + } + + @Override + public ScoreboardGroup getGroup() { + return ScoreboardGroup.FOOTER; + } + + @Override + public int order() { + return Integer.MAX_VALUE; + } + + @Override + public String get(Region region, Player p) { + WorldConfiguration.Misc misc = getConfig(); + switch (misc.redstoneImplementation) { + case ALTERNATE_CURRENT: + switch (misc.alternateCurrentUpdateOrder) { + case VERTICAL_FIRST_INWARD: + return "§eRedstone§8: §cAC §8(§7Y in§8)"; + case VERTICAL_FIRST_OUTWARD: + return "§eRedstone§8: §cAC §8(§7Y out§8)"; + case HORIZONTAL_FIRST_INWARD: + return "§eRedstone§8: §cAC §8(§7XZ in§8)"; + case HORIZONTAL_FIRST_OUTWARD: + return "§eRedstone§8: §cAC §8(§7XZ out§8)"; + } + return "§eRedstone§8: §cAC"; + case EIGENCRAFT: + return null; + case VANILLA: + default: + return "§eRedstone§8: §cVanilla"; + } + } +} 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 new file mode 100644 index 00000000..7afb0e5f --- /dev/null +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/blastresistance/BlastResistanceCommand.java @@ -0,0 +1,72 @@ +/* + * 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.bausystem.features.slaves.blastresistance; + +import com.google.common.util.concurrent.AtomicDouble; +import com.sk89q.worldedit.LocalSession; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.extent.clipboard.Clipboard; +import com.sk89q.worldedit.world.block.BlockState; +import de.steamwar.bausystem.BauSystem; +import de.steamwar.command.SWCommand; +import de.steamwar.linkage.Linked; +import org.bukkit.Material; +import org.bukkit.entity.Player; + +import java.util.concurrent.atomic.AtomicInteger; + +@Linked +public class BlastResistanceCommand extends SWCommand { + + public BlastResistanceCommand() { + super("blastresistance"); + } + + @Register(description = "BLASTRESISTANCE_HELP") + public void command(@Validator Player player) { + LocalSession localSession = WorldEdit.getInstance().getSessionManager().get(BukkitAdapter.adapt(player)); + Clipboard clipboard; + try { + clipboard = localSession.getClipboard().getClipboards().getFirst(); + } catch (WorldEditException e) { + BauSystem.MESSAGE.send("BLASTRESISTANCE_NO_CLIPBOARD", player); + return; + } + + AtomicDouble min = new AtomicDouble(0); + AtomicDouble max = new AtomicDouble(0); + AtomicDouble sum = new AtomicDouble(0); + AtomicInteger count = new AtomicInteger(0); + clipboard.forEach(blockVector3 -> { + BlockState blockState = clipboard.getBlock(blockVector3); + Material material = BukkitAdapter.adapt(blockState).getMaterial(); + if (material == Material.WATER || material == Material.LAVA) return; + double blastResistance = BukkitAdapter.adapt(blockState).getMaterial().getBlastResistance(); + min.set(Math.min(min.get(), blastResistance)); + max.set(Math.max(max.get(), blastResistance)); + sum.addAndGet(blastResistance); + count.getAndIncrement(); + }); + + BauSystem.MESSAGE.send("BLASTRESISTANCE_RESULT", player, min.get(), max.get(), sum.get() / count.get()); + } +} 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/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 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>(reader) + } ?: throw CliktError("GameMode config is empty: ${configFile.absolutePath}") + val serverConfig = document["Server"] as? Map<*, *> + ?: throw CliktError("GameMode config is missing Server section: ${configFile.absolutePath}") + val folder = serverConfig["Folder"] as? String + ?: throw CliktError("GameMode config is missing Server.Folder: ${configFile.absolutePath}") + val maps = (serverConfig["Maps"] as? List<*>) + ?.filterIsInstance() + ?.takeIf { it.isNotEmpty() } + ?: throw CliktError("GameMode config is missing Server.Maps: ${configFile.absolutePath}") + + val serverDir = File("/servers", folder) + val worldTemplate = File(File(serverDir, "arenas"), maps[Random.nextInt(maps.size)]) + return GameModeTemplate( + serverDir = serverDir, + worldTemplate = worldTemplate, + spigot = serverConfig["Spigot"] == true + ) + } + fun setLogConfig(args: MutableList) { args += "-DlogPath=${workingDir.absolutePath}/logs" args += "-Dlog4j.configurationFile=${log4jConfig.absolutePath}" @@ -191,4 +243,4 @@ class DevCommand : CliktCommand("dev") { Runtime.getRuntime().addShutdownHook(Thread { if (process.isAlive) process.destroyForcibly() }) process.waitFor() } -} \ No newline at end of file +} 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/Fight.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/Fight.java index 2d2c8acb..8a894013 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/Fight.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/Fight.java @@ -24,6 +24,7 @@ import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.record.GlobalRecorder; import lombok.Getter; import org.bukkit.Bukkit; +import org.bukkit.Registry; import org.bukkit.Sound; import org.bukkit.entity.LivingEntity; @@ -88,7 +89,7 @@ public class Fight { } public static void playSound(Sound sound, float volume, float pitch) { - GlobalRecorder.getInstance().soundAtPlayer(sound.name(), volume, pitch); + GlobalRecorder.getInstance().soundAtPlayer(Registry.SOUNDS.getKey(sound).getKey(), volume, pitch); //volume: max. 100, pitch: max. 2 Bukkit.getServer().getOnlinePlayers().forEach(player -> player.playSound(player, sound, volume, pitch)); } 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/EntityDamage.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/EntityDamage.java index aa836712..dcc29026 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/EntityDamage.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/EntityDamage.java @@ -24,6 +24,7 @@ import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.StateDependentListener; import de.steamwar.linkage.Linked; +import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.entity.EntityDamageByEntityEvent; @@ -38,11 +39,13 @@ public class EntityDamage implements Listener { @EventHandler public void handleEntityDamage(EntityDamageEvent event) { + if (!(event.getEntity() instanceof Player)) return; if (Config.ArenaRegion.in2dRegion(event.getEntity().getLocation())) event.setCancelled(true); } @EventHandler public void handleEntityDamageByEntity(EntityDamageByEntityEvent event) { + if (!(event.getEntity() instanceof Player)) return; if (Config.ArenaRegion.in2dRegion(event.getEntity().getLocation())) event.setCancelled(true); } } 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> 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 b62ef662..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; @@ -510,11 +512,13 @@ public class PacketProcessor implements Listener { float volume = source.readFloat(); float pitch = source.readFloat(); - Sound sound = Sound.valueOf(soundName); + Sound sound = Registry.SOUNDS.get(NamespacedKey.minecraft(soundName)); + if (sound == null) sound = Sound.valueOf(soundName); // TODO: Remove in 26.x because of no longer needed backwards compatibility + Sound finalSound = sound; execSync(() -> { Location location = new Location(Config.world, x, y, z); - location.getWorld().playSound(location, sound, SoundCategory.valueOf(soundCategory), volume, pitch); + location.getWorld().playSound(location, finalSound, SoundCategory.valueOf(soundCategory), volume, pitch); }); } @@ -524,9 +528,11 @@ public class PacketProcessor implements Listener { float volume = source.readFloat(); float pitch = source.readFloat(); - Sound sound = Sound.valueOf(soundName); + Sound sound = Registry.SOUNDS.get(NamespacedKey.minecraft(soundName)); + if (sound == null) sound = Sound.valueOf(soundName); // TODO: Remove in 26.x because of no longer needed backwards compatibility + Sound finalSound = sound; - execSync(() -> Fight.playSound(sound, volume, pitch)); + execSync(() -> Fight.playSound(finalSound, volume, pitch)); } private void pasteSchem(FightTeam team) throws IOException { diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/Recorder.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/Recorder.java index 69f2bbcb..2d42c473 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/Recorder.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/Recorder.java @@ -33,6 +33,7 @@ import de.steamwar.sql.SchematicNode; import de.steamwar.sql.SteamwarUser; import org.bukkit.Bukkit; import org.bukkit.Location; + import org.bukkit.Registry; import org.bukkit.block.Block; import org.bukkit.craftbukkit.block.CraftBlockState; import org.bukkit.entity.Entity; @@ -238,7 +239,7 @@ public interface Recorder { } default void sound(int x, int y, int z, SWSound soundType, String soundCategory, float volume, float pitch) { - write(0x32, x, y, z, soundType.getSound().name(), soundCategory, volume, pitch); + write(0x32, x, y, z, Registry.SOUNDS.getKey(soundType.getSound()).getKey(), soundCategory, volume, pitch); } default void soundAtPlayer(String soundType, float volume, float pitch) { 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/FightSystem/build.gradle.kts b/FightSystem/build.gradle.kts index c6b14b8b..7079e0f1 100644 --- a/FightSystem/build.gradle.kts +++ b/FightSystem/build.gradle.kts @@ -30,16 +30,6 @@ dependencies { implementation(project(":FightSystem:FightSystem_Core")) } -tasks.register("WarGear20") { - group = "run" - description = "Run a WarGear 1.20 Fight Server" - dependsOn(":SpigotCore:shadowJar") - dependsOn(":FightSystem:shadowJar") - template = "WarGear20" - worldName = "arenas/Pentraki" - config = "WarGear20.yml" -} - tasks.register("HalloweenWS") { group = "run" description = "Run a Halloween 1.21 Fight Replay Server" @@ -63,23 +53,3 @@ tasks.register("WarGear21") { config = "WarGear21.yml" jar = "/jars/paper-1.21.6.jar" } - -tasks.register("SpaceCraftDev20") { - group = "run" - description = "Run a SpaceCraftDev 1.20 Fight Server" - dependsOn(":SpigotCore:shadowJar") - dependsOn(":FightSystem:shadowJar") - template = "SpaceCraft20" - worldName = "arenas/AS_Horizon" - config = "SpaceCraftDev20.yml" -} - -tasks.register("QuickGear20") { - group = "run" - description = "Run a QuickGear 1.20 Fight Server" - dependsOn(":SpigotCore:shadowJar") - dependsOn(":FightSystem:shadowJar") - template = "QuickGear20" - worldName = "arenas/WarGearPark" - config = "QuickGear20.yml" -} \ No newline at end of file diff --git a/LobbySystem/build.gradle.kts b/LobbySystem/build.gradle.kts index a97d8394..b2517c75 100644 --- a/LobbySystem/build.gradle.kts +++ b/LobbySystem/build.gradle.kts @@ -19,6 +19,7 @@ plugins { steamwar.java + widener } dependencies { @@ -32,11 +33,16 @@ dependencies { compileOnly(libs.fawe) } -tasks.register("DevLobby20") { +widener { + fromCatalog(libs.nms) + fromCatalog(libs.paperapi) +} + +tasks.register("DevLobby") { group = "run" - description = "Run a 1.20 Dev Lobby" + description = "Run a Dev Lobby" dependsOn(":SpigotCore:shadowJar") dependsOn(":LobbySystem:jar") - template = "Lobby20" + template = "Lobby21" worldName = "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/SchematicSystem/src/de/steamwar/schematicsystem/autocheck/AutoChecker.java b/SchematicSystem/src/de/steamwar/schematicsystem/autocheck/AutoChecker.java index 1c3a5272..91f90f18 100644 --- a/SchematicSystem/src/de/steamwar/schematicsystem/autocheck/AutoChecker.java +++ b/SchematicSystem/src/de/steamwar/schematicsystem/autocheck/AutoChecker.java @@ -37,8 +37,8 @@ public class AutoChecker { public static final AutoChecker impl = new AutoChecker(); public AutoCheckerResult check(Clipboard clipboard, GameModeConfig type) { - return AutoCheckerResult.builder().type(type).height(clipboard.getDimensions().getBlockY()).width(clipboard.getDimensions().getBlockX()) - .depth(clipboard.getDimensions().getBlockZ()).blockScanResult(scan(clipboard, type)) + return AutoCheckerResult.builder().type(type).height(clipboard.getDimensions().x()).width(clipboard.getDimensions().x()) + .depth(clipboard.getDimensions().z()).blockScanResult(scan(clipboard, type)) .entities(clipboard.getEntities().stream().map(Entity::getLocation) .map(blockVector3 -> new BlockPos(blockVector3.getBlockX(), blockVector3.getBlockY(), blockVector3.getBlockZ())) .collect(Collectors.toList())) @@ -46,19 +46,19 @@ public class AutoChecker { } public AutoCheckerResult sizeCheck(Clipboard clipboard, GameModeConfig type) { - return AutoCheckerResult.builder().type(type).height(clipboard.getDimensions().getBlockY()).width(clipboard.getDimensions().getBlockX()) - .depth(clipboard.getDimensions().getBlockZ()).build(); + return AutoCheckerResult.builder().type(type).height(clipboard.getDimensions().y()).width(clipboard.getDimensions().x()) + .depth(clipboard.getDimensions().z()).build(); } public AutoChecker.BlockScanResult scan(Clipboard clipboard, GameModeConfig type) { AutoChecker.BlockScanResult result = new AutoChecker.BlockScanResult(); BlockVector3 min = clipboard.getMinimumPoint(); BlockVector3 max = clipboard.getMaximumPoint(); - for (int x = min.getBlockX(); x <= max.getBlockX(); x++) { - for (int y = min.getBlockY(); y <= max.getBlockY(); y++) { - for (int z = min.getBlockZ(); z <= max.getBlockZ(); z++) { + for (int x = min.x(); x <= max.x(); x++) { + for (int y = min.y(); y <= max.y(); y++) { + for (int z = min.z(); z <= max.z(); z++) { final BaseBlock block = clipboard.getFullBlock(BlockVector3.at(x, y, z)); - final Material material = Material.matchMaterial(block.getBlockType().getId()); + final Material material = Material.matchMaterial(block.getBlockType().id()); if (material == null) { continue; } @@ -69,7 +69,7 @@ public class AutoChecker { checkInventory(result, block, material, new BlockPos(x, y, z), type); } - if (x == min.getBlockX() || x == max.getBlockX() || y == max.getBlockY() || z == min.getBlockZ() || z == max.getBlockZ()) { + if (x == min.x() || x == max.x() || y == max.y() || z == min.z() || z == max.z()) { result.getDesignBlocks().computeIfAbsent(material, m -> new ArrayList<>()).add(new BlockPos(x, y, z)); } } diff --git a/SchematicSystem/src/de/steamwar/schematicsystem/commands/schematiccommand/SchematicCommand.java b/SchematicSystem/src/de/steamwar/schematicsystem/commands/schematiccommand/SchematicCommand.java index 1fb72577..4e5ecdf7 100644 --- a/SchematicSystem/src/de/steamwar/schematicsystem/commands/schematiccommand/SchematicCommand.java +++ b/SchematicSystem/src/de/steamwar/schematicsystem/commands/schematiccommand/SchematicCommand.java @@ -245,12 +245,12 @@ public class SchematicCommand extends SWCommand { BlockState replaceType = Objects.requireNonNull(toReplace.contains(Material.END_STONE) ? BlockTypes.IRON_BLOCK : BlockTypes.END_STONE).getDefaultState(); BlockVector3 min = clipboard.getMinimumPoint(); BlockVector3 max = clipboard.getMaximumPoint(); - for (int i = min.getBlockX(); i <= max.getBlockX(); i++) { - for (int j = min.getBlockY(); j <= max.getBlockY(); j++) { - for (int k = min.getBlockZ(); k <= max.getBlockZ(); k++) { + for (int i = min.x(); i <= max.x(); i++) { + for (int j = min.y(); j <= max.y(); j++) { + for (int k = min.z(); k <= max.z(); k++) { BlockVector3 vector = BlockVector3.at(i, j, k); BaseBlock block = clipboard.getFullBlock(vector); - if (toReplace.contains(Material.matchMaterial(block.getBlockType().getId()))) { + if (toReplace.contains(Material.matchMaterial(block.getBlockType().id()))) { clipboard.setBlock(vector, replaceType.toBaseBlock()); } } diff --git a/SpigotCore/SpigotCore_Main/build.gradle.kts b/SpigotCore/SpigotCore_Main/build.gradle.kts index a5e3227d..13fe0e40 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 { @@ -58,6 +59,8 @@ dependencies { compileOnly(libs.netty) compileOnly(libs.brigadier) compileOnly(libs.fastutil) - - 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 list = Reflection.getField(serverConnection.getClass(), List.class, i).get(serverConnection); - - for (Object item : list) { - if (!(item instanceof ChannelFuture)) break; - - // Channel future that contains the server connection - Channel serverChannel = ((ChannelFuture) item).channel(); - - serverChannels.add(serverChannel); - serverChannel.pipeline().addFirst(serverChannelHandler); - looking = false; - } + serverChannels.add(serverChannel); + serverChannel.pipeline().addFirst(serverChannelHandler); } } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/Reflection.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/Reflection.java deleted file mode 100644 index d75fdae7..00000000 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/Reflection.java +++ /dev/null @@ -1,315 +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; - -import de.steamwar.core.Core; -import jdk.internal.misc.Unsafe; -import lombok.AllArgsConstructor; -import lombok.experimental.UtilityClass; -import org.bukkit.Bukkit; - -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - - -@UtilityClass -public final class Reflection { - - public static final int MAJOR_VERSION; - public static final int MINOR_VERSION; - - static { - String[] version = Bukkit.getServer().getBukkitVersion().split("-")[0].split("\\."); - MAJOR_VERSION = Integer.parseInt(version[1]); - MINOR_VERSION = version.length > 2 ? Integer.parseInt(version[2]) : 0; - } - - private static final String ORG_BUKKIT_CRAFTBUKKIT = Bukkit.getServer().getClass().getPackage().getName(); - public static final String LEGACY_NET_MINECRAFT_SERVER = ORG_BUKKIT_CRAFTBUKKIT.replace("org.bukkit.craftbukkit", "net.minecraft.server"); - - private static final Map spigotClassnames = new HashMap<>(); - - static { - // See https://mappings.dev for complete mappings - spigotClassnames.put("net.minecraft.Util", "net.minecraft.SystemUtils"); - - spigotClassnames.put("net.minecraft.core.BlockPos", "net.minecraft.core.BlockPosition"); - spigotClassnames.put("net.minecraft.core.DefaultedRegistry", "net.minecraft.core.RegistryBlocks"); - spigotClassnames.put("net.minecraft.core.IdMapper", "net.minecraft.core.RegistryBlockID"); - spigotClassnames.put("net.minecraft.core.Vec3i", "net.minecraft.core.BaseBlockPosition"); - - spigotClassnames.put("net.minecraft.nbt.CompoundTag", "net.minecraft.nbt.NBTTagCompound"); - - spigotClassnames.put("net.minecraft.network.Connection", "net.minecraft.network.NetworkManager"); - - spigotClassnames.put("net.minecraft.network.chat.Component", "net.minecraft.network.chat.IChatBaseComponent"); - - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundAddEntityPacket", "net.minecraft.network.protocol.game.PacketPlayOutSpawnEntity"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundAddPlayerPacket", "net.minecraft.network.protocol.game.PacketPlayOutNamedEntitySpawn"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundAnimatePacket", "net.minecraft.network.protocol.game.PacketPlayOutAnimation"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundBlockDestructionPacket", "net.minecraft.network.protocol.game.PacketPlayOutBlockBreak"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket", "net.minecraft.network.protocol.game.PacketPlayOutTileEntityData"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundBlockEventPacket", "net.minecraft.network.protocol.game.PacketPlayOutBlockAction"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket", "net.minecraft.network.protocol.game.PacketPlayOutBlockChange"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundContainerClosePacket", "net.minecraft.network.protocol.game.PacketPlayOutCloseWindow"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundEntityEventPacket", "net.minecraft.network.protocol.game.PacketPlayOutEntityStatus"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundExplodePacket", "net.minecraft.network.protocol.game.PacketPlayOutExplosion"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundGameEventPacket", "net.minecraft.network.protocol.game.PacketPlayOutGameStateChange"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData$BlockEntityInfo", "net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData$a"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundLevelEventPacket", "net.minecraft.network.protocol.game.PacketPlayOutWorldEvent"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundLevelParticlesPacket", "net.minecraft.network.protocol.game.PacketPlayOutWorldParticles"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundMoveEntityPacket", "net.minecraft.network.protocol.game.PacketPlayOutEntity"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$Pos", "net.minecraft.network.protocol.game.PacketPlayOutEntity$PacketPlayOutRelEntityMove"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$PosRot", "net.minecraft.network.protocol.game.PacketPlayOutEntity$PacketPlayOutRelEntityMoveLook"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$Rot", "net.minecraft.network.protocol.game.PacketPlayOutEntity$PacketPlayOutEntityLook"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundOpenSignEditorPacket", "net.minecraft.network.protocol.game.PacketPlayOutOpenSignEditor"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket", "net.minecraft.network.protocol.game.PacketPlayOutEntityDestroy"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundRotateHeadPacket", "net.minecraft.network.protocol.game.PacketPlayOutEntityHeadRotation"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket", "net.minecraft.network.protocol.game.PacketPlayOutMultiBlockChange"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket", "net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundSetEntityMotionPacket", "net.minecraft.network.protocol.game.PacketPlayOutEntityVelocity"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket", "net.minecraft.network.protocol.game.PacketPlayOutEntityEquipment"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundSetObjectivePacket", "net.minecraft.network.protocol.game.PacketPlayOutScoreboardObjective"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundSetScorePacket", "net.minecraft.network.protocol.game.PacketPlayOutScoreboardScore"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundSoundPacket", "net.minecraft.network.protocol.game.PacketPlayOutNamedSoundEffect"); - spigotClassnames.put("net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket", "net.minecraft.network.protocol.game.PacketPlayOutEntityTeleport"); - spigotClassnames.put("net.minecraft.network.protocol.game.ServerboundContainerClickPacket", "net.minecraft.network.protocol.game.PacketPlayInWindowClick"); - spigotClassnames.put("net.minecraft.network.protocol.game.ServerboundInteractPacket", "net.minecraft.network.protocol.game.PacketPlayInUseEntity"); - spigotClassnames.put("net.minecraft.network.protocol.game.ServerboundInteractPacket$Action", "net.minecraft.network.protocol.game.PacketPlayInUseEntity$EnumEntityUseAction"); - spigotClassnames.put("net.minecraft.network.protocol.game.ServerboundInteractPacket$ActionType", "net.minecraft.network.protocol.game.PacketPlayInUseEntity$b"); - spigotClassnames.put("net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$Pos", "net.minecraft.network.protocol.game.PacketPlayInFlying$PacketPlayInPosition"); - spigotClassnames.put("net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$PosRot", "net.minecraft.network.protocol.game.PacketPlayInFlying$PacketPlayInPositionLook"); - spigotClassnames.put("net.minecraft.network.protocol.game.ServerboundMovePlayerPacket$Rot", "net.minecraft.network.protocol.game.PacketPlayInFlying$PacketPlayInLook"); - spigotClassnames.put("net.minecraft.network.protocol.game.ServerboundPlayerActionPacket", "net.minecraft.network.protocol.game.PacketPlayInBlockDig"); - spigotClassnames.put("net.minecraft.network.protocol.game.ServerboundSetCreativeModeSlotPacket", "net.minecraft.network.protocol.game.PacketPlayInSetCreativeSlot"); - spigotClassnames.put("net.minecraft.network.protocol.game.ServerboundSignUpdatePacket", "net.minecraft.network.protocol.game.PacketPlayInUpdateSign"); - spigotClassnames.put("net.minecraft.network.protocol.game.ServerboundUseItemPacket", "net.minecraft.network.protocol.game.PacketPlayInBlockPlace"); - spigotClassnames.put("net.minecraft.network.protocol.game.ServerboundUseItemOnPacket", "net.minecraft.network.protocol.game.PacketPlayInUseItem"); - - spigotClassnames.put("net.minecraft.network.syncher.EntityDataAccessor", "net.minecraft.network.syncher.DataWatcherObject"); - spigotClassnames.put("net.minecraft.network.syncher.EntityDataSerializer", "net.minecraft.network.syncher.DataWatcherSerializer"); - spigotClassnames.put("net.minecraft.network.syncher.EntityDataSerializers", "net.minecraft.network.syncher.DataWatcherRegistry"); - spigotClassnames.put("net.minecraft.network.syncher.SynchedEntityData$DataItem", "net.minecraft.network.syncher.DataWatcher$Item"); - - spigotClassnames.put("net.minecraft.server.ServerScoreboard$Method", "net.minecraft.server.ScoreboardServer$Action"); - - spigotClassnames.put("net.minecraft.server.level.ChunkMap", "net.minecraft.server.level.PlayerChunkMap"); - spigotClassnames.put("net.minecraft.server.level.ChunkMap$TrackedEntity", "net.minecraft.server.level.PlayerChunkMap$EntityTracker"); - spigotClassnames.put("net.minecraft.server.level.ServerChunkCache", "net.minecraft.server.level.ChunkProviderServer"); - spigotClassnames.put("net.minecraft.server.level.ServerLevel", "net.minecraft.server.level.WorldServer"); - spigotClassnames.put("net.minecraft.server.level.ServerPlayer", "net.minecraft.server.level.EntityPlayer"); - - spigotClassnames.put("net.minecraft.server.network.ServerConnectionListener", "net.minecraft.server.network.ServerConnection"); - - spigotClassnames.put("net.minecraft.world.InteractionHand", "net.minecraft.world.EnumHand"); - - spigotClassnames.put("net.minecraft.world.entity.EntityType", "net.minecraft.world.entity.EntityTypes"); - spigotClassnames.put("net.minecraft.world.entity.Pose", "net.minecraft.world.entity.EntityPose"); - - spigotClassnames.put("net.minecraft.world.entity.item.PrimedTnt", "net.minecraft.world.entity.item.EntityTNTPrimed"); - - spigotClassnames.put("net.minecraft.world.entity.projectile.AbstractArrow", "net.minecraft.world.entity.projectile.EntityArrow"); - - spigotClassnames.put("net.minecraft.world.level.GameType", "net.minecraft.world.level.EnumGamemode"); - spigotClassnames.put("net.minecraft.world.level.LevelAccessor", "net.minecraft.world.level.GeneratorAccess"); - - spigotClassnames.put("net.minecraft.world.level.block.state.BlockState", "net.minecraft.world.level.block.state.IBlockData"); - spigotClassnames.put("net.minecraft.world.level.block.state.StateDefinition", "net.minecraft.world.level.block.state.BlockStateList"); - - spigotClassnames.put("net.minecraft.world.level.chunk.LevelChunk", "net.minecraft.world.level.chunk.Chunk"); - - spigotClassnames.put("net.minecraft.world.level.material.FlowingFluid", "net.minecraft.world.level.material.FluidTypeFlowing"); - spigotClassnames.put("net.minecraft.world.level.material.Fluids", "net.minecraft.world.level.material.FluidTypes"); - spigotClassnames.put("net.minecraft.world.level.material.FluidState", "net.minecraft.world.level.material.Fluid"); - - spigotClassnames.put("net.minecraft.world.phys.BlockHitResult", "net.minecraft.world.phys.MovingObjectPositionBlock"); - spigotClassnames.put("net.minecraft.world.phys.Vec3", "net.minecraft.world.phys.Vec3D"); - - spigotClassnames.put("net.minecraft.resources.ResourceLocation", "net.minecraft.resources.MinecraftKey"); - - spigotClassnames.put("net.minecraft.util.ProgressListener", "net.minecraft.util.IProgressUpdate"); - } - - public static Class getClass(String name) { - try { - if (name.startsWith("org.bukkit.craftbukkit")) { - return Class.forName(ORG_BUKKIT_CRAFTBUKKIT + name.substring(22)); - } else if (MAJOR_VERSION < 17 && name.startsWith("net.minecraft")) { - return Class.forName(LEGACY_NET_MINECRAFT_SERVER + "." + spigotClassnames.getOrDefault(name, name).split("[.](?=[^.]*$)")[1]); - } else if (MAJOR_VERSION < 21 || MINOR_VERSION < 4) { - return Class.forName(spigotClassnames.getOrDefault(name, name)); - } else { - Class clazz = null; - try { - clazz = Class.forName(name); - } catch (ClassNotFoundException e) { - } - if (clazz != null && clazz.getName().equals(name)) { - return clazz; - } - - try { - return Core.class.getClassLoader().getParent().loadClass(name); - } catch (ClassNotFoundException e) { - if (clazz == null) { - throw e; - } - - return clazz; - } - } - } catch (ClassNotFoundException e) { - throw new IllegalArgumentException("Cannot find " + name, e); - } - } - - @AllArgsConstructor - public static class Field { - private final java.lang.reflect.Field f; - - @SuppressWarnings("unchecked") - public T get(Object target) { - try { - return (T) f.get(target); - } catch (IllegalAccessException e) { - throw new IllegalArgumentException("Cannot read field", e); - } - } - - public void set(Object target, Object value) { - try { - f.set(target, value); - } catch (IllegalAccessException e) { - throw new IllegalArgumentException("Cannot write field", e); - } - } - } - - public static Field getField(Class target, String name, Class fieldType) { - return getField(target, name, fieldType, 0); - } - - public static Field getField(Class target, Class fieldType, int index) { - return getField(target, null, fieldType, index); - } - - public static Field getField(Class target, Class fieldType, int index, Class... parameters) { - return getField(target, null, fieldType, index, parameters); - } - - private static Field getField(Class target, String name, Class fieldType, int index, Class... parameters) { - for (final java.lang.reflect.Field field : target.getDeclaredFields()) { - if (matching(field, name, fieldType, parameters) && index-- <= 0) { - field.setAccessible(true); - return new Field<>(field); - } - } - - // Search in parent classes - if (target.getSuperclass() != null) { - return getField(target.getSuperclass(), name, fieldType, index); - } - - throw new IllegalArgumentException("Cannot find field with type " + fieldType); - } - - private static boolean matching(java.lang.reflect.Field field, String name, Class fieldType, Class... parameters) { - if (name != null && !field.getName().equals(name)) return false; - - if (!fieldType.isAssignableFrom(field.getType())) return false; - - if (parameters.length > 0) { - Type[] arguments = ((ParameterizedType) field.getGenericType()).getActualTypeArguments(); - - for (int i = 0; i < parameters.length; i++) { - if (arguments[i] instanceof ParameterizedType ? ((ParameterizedType) arguments[i]).getRawType() != parameters[i] : arguments[i] != parameters[i]) { - return false; - } - } - } - return true; - } - - @AllArgsConstructor - public static class Method { - private final java.lang.reflect.Method m; - - public Object invoke(Object target, Object... arguments) { - try { - return m.invoke(target, arguments); - } catch (Exception e) { - throw new IllegalArgumentException("Cannot invoke method " + m, e); - } - } - } - - public static Method getTypedMethod(Class clazz, String methodName, Class returnType, Class... params) { - for (final java.lang.reflect.Method method : clazz.getDeclaredMethods()) { - if ((methodName == null || method.getName().equals(methodName)) - && (returnType == null || method.getReturnType().equals(returnType)) - && Arrays.equals(method.getParameterTypes(), params)) { - method.setAccessible(true); - return new Method(method); - } - } - - // Search in every superclass - if (clazz.getSuperclass() != null) { - return getTypedMethod(clazz.getSuperclass(), methodName, returnType, params); - } - - throw new IllegalArgumentException(String.format("Cannot find method %s (%s).", methodName, Arrays.asList(params))); - } - - @AllArgsConstructor - public static class Constructor { - private final java.lang.reflect.Constructor c; - - public Object invoke(Object... arguments) { - try { - return c.newInstance(arguments); - } catch (Exception e) { - throw new IllegalArgumentException("Cannot invoke constructor " + c, e); - } - } - } - - public static Constructor getConstructor(Class clazz, Class... params) { - for (final java.lang.reflect.Constructor constructor : clazz.getDeclaredConstructors()) { - if (Arrays.equals(constructor.getParameterTypes(), params)) { - constructor.setAccessible(true); - return new Constructor(constructor); - } - } - - throw new IllegalStateException(String.format("Unable to find constructor for %s (%s).", clazz, Arrays.asList(params))); - } - - public static Object newInstance(Class clazz) { - try { - return Unsafe.getUnsafe().allocateInstance(clazz); - } catch (InstantiationException e) { - throw new SecurityException("Could not create object", e); - } - } -} diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/command/CommandRegistering.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/command/CommandRegistering.java index 4adffb58..90026343 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/command/CommandRegistering.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/command/CommandRegistering.java @@ -22,35 +22,20 @@ package de.steamwar.command; import lombok.experimental.UtilityClass; import org.bukkit.Bukkit; import org.bukkit.command.Command; -import org.bukkit.command.CommandMap; import org.bukkit.command.SimpleCommandMap; +import org.bukkit.craftbukkit.CraftServer; -import java.lang.reflect.Field; import java.util.Map; @UtilityClass class CommandRegistering { - private static final CommandMap commandMap; + private static final SimpleCommandMap commandMap; private static final Map knownCommandMap; static { - try { - final Field commandMapField = Bukkit.getServer().getClass().getDeclaredField("commandMap"); - commandMapField.setAccessible(true); - commandMap = (CommandMap) commandMapField.get(Bukkit.getServer()); - } catch (NoSuchFieldException | IllegalAccessException exception) { - Bukkit.shutdown(); - throw new SecurityException("Oh shit. Commands cannot be registered.", exception); - } - try { - final Field knownCommandsField = SimpleCommandMap.class.getDeclaredField("knownCommands"); - knownCommandsField.setAccessible(true); - knownCommandMap = (Map) knownCommandsField.get(commandMap); - } catch (NoSuchFieldException | IllegalAccessException exception) { - Bukkit.shutdown(); - throw new SecurityException("Oh shit. Commands cannot be registered.", exception); - } + commandMap = ((CraftServer) Bukkit.getServer()).getCommandMap(); + knownCommandMap = commandMap.getKnownCommands(); } static void unregister(Command command) { diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/BountifulWrapper.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/BountifulWrapper.java index e5a69d9f..f02a59f7 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/BountifulWrapper.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/BountifulWrapper.java @@ -19,20 +19,11 @@ package de.steamwar.core; -import de.steamwar.Reflection; import net.md_5.bungee.api.ChatMessageType; import net.md_5.bungee.api.chat.BaseComponent; -import net.minecraft.network.syncher.EntityDataAccessor; -import net.minecraft.network.syncher.EntityDataSerializer; -import net.minecraft.network.syncher.EntityDataSerializers; -import net.minecraft.network.syncher.SynchedEntityData; -import net.minecraft.world.entity.PositionMoveRotation; -import net.minecraft.world.phys.Vec3; import org.bukkit.Sound; import org.bukkit.entity.Player; -import java.util.UUID; - public class BountifulWrapper { public static final BountifulWrapper impl = new BountifulWrapper(); @@ -44,60 +35,4 @@ public class BountifulWrapper { if (type == ChatMessageType.CHAT) type = ChatMessageType.SYSTEM; player.spigot().sendMessage(type, msg); } - - private static final Class dataWatcherRegistry = EntityDataSerializers.class; - private static final Class dataWatcherSerializer = EntityDataSerializer.class; - - public Object getDataWatcherObject(int index, Class type) { - return new EntityDataAccessor<>(index, (EntityDataSerializer) Reflection.getField(dataWatcherRegistry, dataWatcherSerializer, 0, type).get(null)); - } - - public Object getDataWatcherItem(Object dwo, Object value) { - return new SynchedEntityData.DataItem<>((EntityDataAccessor) dwo, value); - } - - public BountifulWrapper.PositionSetter getPositionSetter(Class packetClass, int fieldOffset) { - try { - Reflection.Field field = Reflection.getField(packetClass, PositionMoveRotation.class, 0); - - return (packet, x, y, z, pitch, yaw) -> { - field.set(packet, new PositionMoveRotation(new Vec3(x, y, z), field.get(packet).deltaMovement(), yaw, pitch)); - }; - } catch (IllegalArgumentException e) { - Reflection.Field posX = Reflection.getField(packetClass, double.class, fieldOffset); - Reflection.Field posY = Reflection.getField(packetClass, double.class, fieldOffset + 1); - Reflection.Field posZ = Reflection.getField(packetClass, double.class, fieldOffset + 2); - boolean isByteClass = packetClass.getSimpleName().contains("PacketPlayOutEntityTeleport") || packetClass.getSimpleName().contains("PacketPlayOutNamedEntitySpawn"); - Class pitchYawType = isByteClass ? byte.class : int.class; - Reflection.Field lookYaw = Reflection.getField(packetClass, pitchYawType, isByteClass ? 0 : 1); - Reflection.Field lookPitch = Reflection.getField(packetClass, pitchYawType, isByteClass ? 1 : 2); - - return (packet, x, y, z, pitch, yaw) -> { - posX.set(packet, x); - posY.set(packet, y); - posZ.set(packet, z); - if (isByteClass) { - lookYaw.set(packet, (byte) (yaw * 256 / 360)); - lookPitch.set(packet, (byte) (pitch * 256 / 360)); - } else { - lookYaw.set(packet, (int) (yaw * 256 / 360)); - lookPitch.set(packet, (int) (pitch * 256 / 360)); - } - }; - } - } - - public BountifulWrapper.UUIDSetter getUUIDSetter(Class packetClass) { - Reflection.Field uuidField = Reflection.getField(packetClass, UUID.class, 0); - - return uuidField::set; - } - - public interface PositionSetter { - void set(Object packet, double x, double y, double z, float pitch, float yaw); - } - - public interface UUIDSetter { - void set(Object packet, UUID uuid); - } } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/CheckpointUtilsJ9.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/CheckpointUtilsJ9.java index 4ccbaaa7..2849b5a4 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/CheckpointUtilsJ9.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/CheckpointUtilsJ9.java @@ -20,7 +20,6 @@ package de.steamwar.core; import com.comphenix.tinyprotocol.TinyProtocol; -import de.steamwar.Reflection; import de.steamwar.sql.internal.Statement; import io.netty.channel.ChannelFuture; import net.minecraft.server.MinecraftServer; @@ -96,9 +95,6 @@ class CheckpointUtilsJ9 { } } - - private static final Reflection.Field channelFutures = Reflection.getField(ServerConnectionListener.class, List.class, 0, ChannelFuture.class); - private static void freezeInternal(Path path) throws Exception { Bukkit.getPluginManager().callEvent(new CRIUSleepEvent()); @@ -109,9 +105,9 @@ class CheckpointUtilsJ9 { // Close socket ServerConnectionListener serverConnection = MinecraftServer.getServer().getConnection(); - List channels = channelFutures.get(serverConnection); - for (Object future : channels) { - ((ChannelFuture) future).channel().close().syncUninterruptibly(); + List channels = serverConnection.channels; + for (ChannelFuture future : channels) { + future.channel().close().syncUninterruptibly(); } channels.clear(); @@ -145,8 +141,8 @@ class CheckpointUtilsJ9 { // Reopen socket serverConnection.startTcpServerListener(InetAddress.getLoopbackAddress(), port); - for (Object future : channels) { - ((ChannelFuture) future).channel().config().setAutoRead(true); + for (ChannelFuture future : channels) { + future.channel().config().setAutoRead(true); } Bukkit.getPluginManager().callEvent(new CRIUWakeupEvent()); diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/Core.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/Core.java index a1aefd55..8f076c0a 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/Core.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/Core.java @@ -5,8 +5,8 @@ * * 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. + * the Free Software Foundation, either versionStrings 3 of the License, or + * (at your option) any later versionStrings. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -20,7 +20,6 @@ package de.steamwar.core; import com.comphenix.tinyprotocol.TinyProtocol; -import de.steamwar.Reflection; import de.steamwar.command.SWCommandUtils; import de.steamwar.command.SWTypeMapperCreator; import de.steamwar.command.TabCompletionCache; @@ -49,9 +48,13 @@ public class Core extends JavaPlugin { public static final Message MESSAGE = new Message("SpigotCore", Core.class.getClassLoader()); + @Getter @Deprecated - public static int getVersion() { - return Reflection.MAJOR_VERSION; + private static final int version; + + static { + String[] versionStrings = Bukkit.getServer().getBukkitVersion().split("-")[0].split("\\."); + version = Integer.parseInt(versionStrings[1]); } @Getter diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/ErrorHandler.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/ErrorHandler.java index f60eb406..5995a3c1 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/ErrorHandler.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/ErrorHandler.java @@ -19,7 +19,6 @@ package de.steamwar.core; -import de.steamwar.Reflection; import de.steamwar.sql.SWException; import org.spigotmc.WatchdogThread; @@ -39,9 +38,7 @@ public class ErrorHandler extends Handler { public ErrorHandler() { Logger.getLogger("").addHandler(this); - - Reflection.Field getInstance = Reflection.getField(WatchdogThread.class, WatchdogThread.class, 0); - watchdogThreadId = getInstance.get(null).getId(); + watchdogThreadId = WatchdogThread.instance.threadId(); } void unregister() { diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/WorldIdentifier.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/WorldIdentifier.java index 2ca9d085..2fa27b1e 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/WorldIdentifier.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/WorldIdentifier.java @@ -20,7 +20,6 @@ package de.steamwar.core; import com.comphenix.tinyprotocol.TinyProtocol; -import de.steamwar.Reflection; import de.steamwar.linkage.Linked; import net.minecraft.network.protocol.game.ClientboundLoginPacket; import net.minecraft.network.protocol.game.CommonPlayerSpawnInfo; @@ -33,13 +32,8 @@ public class WorldIdentifier { private static ResourceKey resourceKey = null; - private static final Class resourceKeyClass = ResourceKey.class; - private static final Class minecraftKeyClass = ResourceLocation.class; - private static final Reflection.Constructor resourceKeyConstructor = Reflection.getConstructor(resourceKeyClass, minecraftKeyClass, minecraftKeyClass); - private static final Reflection.Constructor minecraftKeyConstructor = Reflection.getConstructor(minecraftKeyClass, String.class, String.class); - public static void set(String name) { - resourceKey = (ResourceKey) resourceKeyConstructor.invoke(minecraftKeyConstructor.invoke("minecraft", "dimension"), minecraftKeyConstructor.invoke("steamwar", name)); + resourceKey = new ResourceKey<>(new ResourceLocation("minecraft", "dimension"), new ResourceLocation("steamwar", name)); } public WorldIdentifier() { diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/authlib/SteamwarGameProfileRepository.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/authlib/SteamwarGameProfileRepository.java index 32e394e7..dd7540c6 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/authlib/SteamwarGameProfileRepository.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/authlib/SteamwarGameProfileRepository.java @@ -22,7 +22,6 @@ package de.steamwar.core.authlib; import com.mojang.authlib.GameProfile; import com.mojang.authlib.GameProfileRepository; import com.mojang.authlib.ProfileLookupCallback; -import de.steamwar.Reflection; import de.steamwar.sql.SteamwarUser; import net.minecraft.server.MinecraftServer; import net.minecraft.server.Services; @@ -35,13 +34,10 @@ public class SteamwarGameProfileRepository implements GameProfileRepository { public static final SteamwarGameProfileRepository impl = new SteamwarGameProfileRepository(); private static final GameProfileRepository fallback; - private static final Reflection.Field field; private static final Services current; static { - Class clazz = MinecraftServer.getServer().getClass(); - field = Reflection.getField(clazz, Services.class, 0); - current = field.get(MinecraftServer.getServer()); + current = MinecraftServer.getServer().services; fallback = current.profileRepository(); } @@ -68,7 +64,6 @@ public class SteamwarGameProfileRepository implements GameProfileRepository { } public void inject() { - Services newServices = new Services(current.sessionService(), current.servicesKeySet(), this, current.profileCache(), current.paperConfigurations()); - field.set(MinecraftServer.getServer(), newServices); + MinecraftServer.getServer().services = new Services(current.sessionService(), current.servicesKeySet(), this, current.profileCache(), current.paperConfigurations()); } } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/events/AntiNocom.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/events/AntiNocom.java index b5276c00..d3625026 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/events/AntiNocom.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/events/AntiNocom.java @@ -20,16 +20,13 @@ package de.steamwar.core.events; import com.comphenix.tinyprotocol.TinyProtocol; -import de.steamwar.Reflection; import de.steamwar.core.Core; import de.steamwar.linkage.Linked; import de.steamwar.sql.SWException; import de.steamwar.techhider.ProtocolUtils; -import de.steamwar.techhider.TechHider; import net.minecraft.core.BlockPos; import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket; import net.minecraft.network.protocol.game.ServerboundUseItemOnPacket; -import net.minecraft.world.phys.BlockHitResult; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -38,7 +35,6 @@ import org.bukkit.event.player.PlayerQuitEvent; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Function; @Linked public class AntiNocom implements Listener { diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/cursor/CursorListener.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/cursor/CursorListener.java index 2d7b438f..99e286ca 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/cursor/CursorListener.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/cursor/CursorListener.java @@ -13,7 +13,7 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerInteractEvent; -import java.util.*; +import java.util.Optional; @Linked diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RArmorStand.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RArmorStand.java index 746a080e..be389fcb 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RArmorStand.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RArmorStand.java @@ -49,7 +49,7 @@ public class RArmorStand extends REntity implements RInteractableEntity blockWatcher = new EntityDataAccessor<>(23, EntityDataSerializers.BLOCK_STATE); - private void getBlock(boolean ignoreDefault, BiConsumer packetSink) { + private void getBlock(boolean ignoreDefault, EntityDataPacketBuilder packetSink) { if (ignoreDefault || !block.getAsString(true).equals(DEFAULT_BLOCK.getAsString(true))) { - packetSink.accept(blockWatcher, ((CraftBlockData) block).getState()); + packetSink.add(blockWatcher, ((CraftBlockData) block).getState()); } } } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RDisplay.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RDisplay.java index 4ec21947..d96d085c 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RDisplay.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RDisplay.java @@ -89,18 +89,13 @@ public abstract class RDisplay extends REntity { } @SafeVarargs - protected final void sendPacket(Consumer packetSink, BiConsumer>... dataSinkSinks) { - List keyValueData = new ArrayList<>(); + protected final void sendPacket(Consumer packetSink, BiConsumer... dataSinkSinks) { + EntityDataPacketBuilder builder = entityDataPacket(); boolean ignoreDefault = packetSink == updatePacketSink; - for (BiConsumer> dataSinkSink : dataSinkSinks) { - dataSinkSink.accept(ignoreDefault, (dataWatcher, value) -> { - keyValueData.add(dataWatcher); - keyValueData.add(value); - }); - } - if (!keyValueData.isEmpty()) { - packetSink.accept(getDataWatcherPacket(keyValueData.toArray())); + for (BiConsumer dataSinkSink : dataSinkSinks) { + dataSinkSink.accept(ignoreDefault, builder); } + if (!builder.isEmpty()) builder.send(packetSink); } public void setTransform(@NonNull Transformation transform) { @@ -113,12 +108,12 @@ public abstract class RDisplay extends REntity { private static final EntityDataAccessor scaleWatcher = new EntityDataAccessor<>(12, EntityDataSerializers.VECTOR3); private static final EntityDataAccessor rightRotationWatcher = new EntityDataAccessor<>(14, EntityDataSerializers.QUATERNION); - private void getTransformData(boolean ignoreDefault, BiConsumer dataSink) { + private void getTransformData(boolean ignoreDefault, EntityDataPacketBuilder dataSink) { if (ignoreDefault || !transform.equals(DEFAULT_TRANSFORM)) { - dataSink.accept(translationWatcher, transform.getTranslation()); - dataSink.accept(leftRotationWatcher, transform.getLeftRotation()); - dataSink.accept(scaleWatcher, transform.getScale()); - dataSink.accept(rightRotationWatcher, transform.getRightRotation()); + dataSink.add(translationWatcher, transform.getTranslation()); + dataSink.add(leftRotationWatcher, transform.getLeftRotation()); + dataSink.add(scaleWatcher, transform.getScale()); + dataSink.add(rightRotationWatcher, transform.getRightRotation()); } } @@ -130,10 +125,10 @@ public abstract class RDisplay extends REntity { private static final EntityDataAccessor transformationInterpolationDurationWatcher = new EntityDataAccessor<>(9, EntityDataSerializers.INT); private static final EntityDataAccessor positionOrRotationInterpolationDurationWatcher = new EntityDataAccessor<>(10, EntityDataSerializers.INT); - private void getInterpolationDuration(boolean ignoreDefault, BiConsumer packetSink) { + private void getInterpolationDuration(boolean ignoreDefault, EntityDataPacketBuilder packetSink) { if (ignoreDefault || interpolationDelay != 0) { - packetSink.accept(transformationInterpolationDurationWatcher, interpolationDuration); - packetSink.accept(positionOrRotationInterpolationDurationWatcher, interpolationDuration); + packetSink.add(transformationInterpolationDurationWatcher, interpolationDuration); + packetSink.add(positionOrRotationInterpolationDurationWatcher, interpolationDuration); } } @@ -144,9 +139,9 @@ public abstract class RDisplay extends REntity { private static final EntityDataAccessor viewRangeWatcher = new EntityDataAccessor<>(17, EntityDataSerializers.FLOAT); - private void getViewRange(boolean ignoreDefault, BiConsumer packetSink) { + private void getViewRange(boolean ignoreDefault, EntityDataPacketBuilder packetSink) { if (ignoreDefault || viewRange != 1.0F) { - packetSink.accept(viewRangeWatcher, viewRange); + packetSink.add(viewRangeWatcher, viewRange); } } @@ -157,9 +152,9 @@ public abstract class RDisplay extends REntity { private static final EntityDataAccessor shadowRadiusWatcher = new EntityDataAccessor<>(18, EntityDataSerializers.FLOAT); - private void getShadowRadius(boolean ignoreDefault, BiConsumer packetSink) { + private void getShadowRadius(boolean ignoreDefault, EntityDataPacketBuilder packetSink) { if (ignoreDefault || shadowRadius != 0.0F) { - packetSink.accept(shadowRadiusWatcher, shadowRadius); + packetSink.add(shadowRadiusWatcher, shadowRadius); } } @@ -170,9 +165,9 @@ public abstract class RDisplay extends REntity { private static final EntityDataAccessor shadowStrengthWatcher = new EntityDataAccessor<>(19, EntityDataSerializers.FLOAT); - private void getShadowStrength(boolean ignoreDefault, BiConsumer packetSink) { + private void getShadowStrength(boolean ignoreDefault, EntityDataPacketBuilder packetSink) { if (ignoreDefault || shadowStrength != 1.0F) { - packetSink.accept(shadowStrengthWatcher, shadowStrength); + packetSink.add(shadowStrengthWatcher, shadowStrength); } } @@ -183,9 +178,9 @@ public abstract class RDisplay extends REntity { private static final EntityDataAccessor displayWidthWatcher = new EntityDataAccessor<>(20, EntityDataSerializers.FLOAT); - private void getDisplayWidth(boolean ignoreDefault, BiConsumer packetSink) { + private void getDisplayWidth(boolean ignoreDefault, EntityDataPacketBuilder packetSink) { if (ignoreDefault || displayWidth != 0.0F) { - packetSink.accept(displayWidthWatcher, displayWidth); + packetSink.add(displayWidthWatcher, displayWidth); } } @@ -196,9 +191,9 @@ public abstract class RDisplay extends REntity { private static final EntityDataAccessor displayHeightWatcher = new EntityDataAccessor<>(21, EntityDataSerializers.FLOAT); - private void getDisplayHeight(boolean ignoreDefault, BiConsumer packetSink) { + private void getDisplayHeight(boolean ignoreDefault, EntityDataPacketBuilder packetSink) { if (ignoreDefault || displayHeight != 0.0F) { - packetSink.accept(displayHeightWatcher, displayHeight); + packetSink.add(displayHeightWatcher, displayHeight); } } @@ -209,9 +204,9 @@ public abstract class RDisplay extends REntity { private static final EntityDataAccessor interpolationDelayWatcher = new EntityDataAccessor<>(8, EntityDataSerializers.INT); - private void getInterpolationDelay(boolean ignoreDefault, BiConsumer packetSink) { + private void getInterpolationDelay(boolean ignoreDefault, EntityDataPacketBuilder packetSink) { if (ignoreDefault || interpolationDelay != 0) { - packetSink.accept(interpolationDelayWatcher, interpolationDelay); + packetSink.add(interpolationDelayWatcher, interpolationDelay); } } @@ -222,9 +217,9 @@ public abstract class RDisplay extends REntity { private static final EntityDataAccessor billboardWatcher = new EntityDataAccessor<>(15, EntityDataSerializers.BYTE); - private void getBillboard(boolean ignoreDefault, BiConsumer packetSink) { + private void getBillboard(boolean ignoreDefault, EntityDataPacketBuilder packetSink) { if (ignoreDefault || billboard != Display.Billboard.FIXED) { - packetSink.accept(billboardWatcher, (byte) billboard.ordinal()); + packetSink.add(billboardWatcher, (byte) billboard.ordinal()); } } @@ -235,9 +230,9 @@ public abstract class RDisplay extends REntity { private static final EntityDataAccessor glowColorOverrideWatcher = new EntityDataAccessor<>(22, EntityDataSerializers.INT); - private void getGlowColorOverride(boolean ignoreDefault, BiConsumer packetSink) { + private void getGlowColorOverride(boolean ignoreDefault, EntityDataPacketBuilder packetSink) { if (ignoreDefault || glowColorOverride != null) { - packetSink.accept(glowColorOverrideWatcher, glowColorOverride == null ? -1 : glowColorOverride.asARGB()); + packetSink.add(glowColorOverrideWatcher, glowColorOverride == null ? -1 : glowColorOverride.asARGB()); } } @@ -248,9 +243,9 @@ public abstract class RDisplay extends REntity { private static final EntityDataAccessor brightnessWatcher = new EntityDataAccessor<>(16, EntityDataSerializers.INT); - private void getBrightness(boolean ignoreDefault, BiConsumer packetSink) { + private void getBrightness(boolean ignoreDefault, EntityDataPacketBuilder packetSink) { if (ignoreDefault || brightness != null) { - packetSink.accept(brightnessWatcher, brightness == null ? -1 : brightness.getBlockLight() << 4 | brightness.getSkyLight() << 20); + packetSink.add(brightnessWatcher, brightness == null ? -1 : brightness.getBlockLight() << 4 | brightness.getSkyLight() << 20); } } } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/REntity.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/REntity.java index 8ac0c01b..a5be3c41 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/REntity.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/REntity.java @@ -20,13 +20,12 @@ package de.steamwar.entity; import com.mojang.datafixers.util.Pair; -import de.steamwar.Reflection; -import de.steamwar.core.BountifulWrapper; -import de.steamwar.core.FlatteningWrapper; import it.unimi.dsi.fastutil.ints.IntArrayList; import lombok.Getter; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.chat.contents.PlainTextContents; import net.minecraft.network.protocol.game.*; import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataSerializers; @@ -44,7 +43,6 @@ import org.bukkit.inventory.ItemStack; import java.util.*; import java.util.function.Consumer; -import java.util.function.Function; public class REntity { @@ -174,14 +172,10 @@ public class REntity { server.postEntityMove(this, fromX, fromZ); } - private static final Class animationPacket = ClientboundAnimatePacket.class; - private static final Reflection.Field animationEntity = Reflection.getField(animationPacket, int.class, 5); - private static final Reflection.Field animationAnimation = Reflection.getField(animationPacket, int.class, 6); - public void showAnimation(byte animation) { - Object packet = Reflection.newInstance(animationPacket); - animationEntity.set(packet, entityId); - animationAnimation.set(packet, (int) animation); + ClientboundAnimatePacket packet = new ClientboundAnimatePacket(); + packet.id = entityId; + packet.action = animation; server.updateEntity(this, packet); } @@ -189,25 +183,21 @@ public class REntity { server.updateEntity(this, new ClientboundSetEntityMotionPacket(entityId, new Vec3(calcVelocity(dX), calcVelocity(dY), calcVelocity(dZ)))); } - private static final Class statusPacket = ClientboundEntityEventPacket.class; - private static final Reflection.Field statusEntity = Reflection.getField(statusPacket, int.class, 0); - private static final Reflection.Field statusStatus = Reflection.getField(statusPacket, byte.class, 0); - public void showDamage() { - Object packet = Reflection.newInstance(statusPacket); - statusEntity.set(packet, entityId); - statusStatus.set(packet, (byte) 2); + ClientboundEntityEventPacket packet = new ClientboundEntityEventPacket(); + packet.entityId = entityId; + packet.eventId = (byte) 2; server.updateEntity(this, packet); } public void setPose(Pose pose) { this.pose = pose; - server.updateEntity(this, getDataWatcherPacket(poseDataWatcher, pose)); + server.updateEntity(this, entityDataPacket().add(poseDataWatcher, pose).build()); } public void setOnFire(boolean perma) { fireTick = perma ? -1 : 21; - server.updateEntity(this, getDataWatcherPacket(entityStatusWatcher, getEntityStatus())); + server.updateEntity(this, entityDataPacket().add(entityStatusWatcher, getEntityStatus()).build()); } public boolean isOnFire() { @@ -216,20 +206,18 @@ public class REntity { public void setInvisible(boolean invisible) { this.invisible = invisible; - server.updateEntity(this, getDataWatcherPacket(entityStatusWatcher, getEntityStatus())); + server.updateEntity(this, entityDataPacket().add(entityStatusWatcher, getEntityStatus()).build()); } public void setBowDrawn(boolean drawn, boolean offHand) { bowDrawn = drawn; - server.updateEntity(this, getDataWatcherPacket(poseDataWatcher, Pose.SHOOTING)); + server.updateEntity(this, entityDataPacket().add(poseDataWatcher, Pose.SHOOTING).build()); } public void setDisplayName(String displayName) { this.displayName = displayName; - server.updateEntity(this, getDataWatcherPacket( - nameWatcher, FlatteningWrapper.formatDisplayName(displayName), - nameVisibleWatcher, displayName != null - )); + server.updateEntity(this, entityDataPacket().add(nameWatcher, formatDisplayName(displayName)) + .add(nameVisibleWatcher, displayName != null).build()); } public void setItem(Object slot, ItemStack stack) { @@ -243,22 +231,18 @@ public class REntity { public void setNoGravity(boolean noGravity) { this.noGravity = noGravity; - server.updateEntity(this, getDataWatcherPacket(noGravityDataWatcher, noGravity)); + server.updateEntity(this, entityDataPacket().add(noGravityDataWatcher, noGravity).build()); } public void setGlowing(boolean glowing) { this.isGlowing = glowing; - server.updateEntity(this, getDataWatcherPacket(entityStatusWatcher, getEntityStatus())); + server.updateEntity(this, entityDataPacket().add(entityStatusWatcher, getEntityStatus()).build()); } - private static final Function spawnPacketGenerator = entitySpawnPacketGenerator(ClientboundAddEntityPacket.class, 2); - - private static final Reflection.Field additionalData = Reflection.getField(ClientboundAddEntityPacket.class, int.class, 4); - - private Object spawnPacketGenerator() { - Object packet = spawnPacketGenerator.apply(this); - additionalData.set(packet, objectData); - return packet; + private ClientboundAddEntityPacket spawnPacketGenerator() { + ResourceLocation key = CraftNamespacedKey.toMinecraft(entityType.getKey()); + net.minecraft.world.entity.EntityType entityType = BuiltInRegistries.ENTITY_TYPE.get(key).get().value(); + return new ClientboundAddEntityPacket(entityId, uuid, x, y, z, pitch, yaw, entityType, objectData, Vec3.ZERO, 0); } void list(Consumer packetSink) { @@ -276,20 +260,22 @@ public class REntity { } if (pose != Pose.STANDING) { - packetSink.accept(getDataWatcherPacket(poseDataWatcher, pose)); + entityDataPacket().add(poseDataWatcher, pose).send(packetSink); } byte status = getEntityStatus(); if (status != 0) { - packetSink.accept(getDataWatcherPacket(entityStatusWatcher, getEntityStatus())); + entityDataPacket().add(entityStatusWatcher, getEntityStatus()).send(packetSink); } if (displayName != null) { - packetSink.accept(getDataWatcherPacket(nameWatcher, FlatteningWrapper.formatDisplayName(displayName), nameVisibleWatcher, true)); + entityDataPacket().add(nameWatcher, formatDisplayName(displayName)) + .add(nameVisibleWatcher, true) + .send(packetSink); } if (noGravity) { - packetSink.accept(getDataWatcherPacket(noGravityDataWatcher, true)); + entityDataPacket().add(noGravityDataWatcher, true).send(packetSink); } } @@ -297,7 +283,7 @@ public class REntity { if (fireTick > 0) { fireTick--; if (fireTick == 0) { - server.updateEntity(this, getDataWatcherPacket(entityStatusWatcher, getEntityStatus())); + server.updateEntity(this, entityDataPacket().add(entityStatusWatcher, getEntityStatus()).build()); } } } @@ -328,13 +314,29 @@ public class REntity { return status; } - protected Object getDataWatcherPacket(Object... dataWatcherKeyValues) { - ArrayList> nativeWatchers = new ArrayList<>(1); - for (int i = 0; i < dataWatcherKeyValues.length; i += 2) { - nativeWatchers.add(((SynchedEntityData.DataItem) BountifulWrapper.impl.getDataWatcherItem(dataWatcherKeyValues[i], dataWatcherKeyValues[i + 1])).value()); + protected EntityDataPacketBuilder entityDataPacket() { + return new EntityDataPacketBuilder(); + } + + public class EntityDataPacketBuilder { + private List> values = new ArrayList<>(); + + public EntityDataPacketBuilder add(EntityDataAccessor accessor, T value) { + values.add(new SynchedEntityData.DataItem<>(accessor, value).value()); + return this; } - return new ClientboundSetEntityDataPacket(entityId, nativeWatchers); + public boolean isEmpty() { + return values.isEmpty(); + } + + public ClientboundSetEntityDataPacket build() { + return new ClientboundSetEntityDataPacket(entityId, values); + } + + public void send(Consumer packetSink) { + packetSink.accept(build()); + } } private Object getTeleportPacket() { @@ -359,14 +361,10 @@ public class REntity { } } - private static final Class headRotationPacket = ClientboundRotateHeadPacket.class; - private static final Reflection.Field headRotationEntity = Reflection.getField(headRotationPacket, int.class, 0); - private static final Reflection.Field headRotationYaw = Reflection.getField(headRotationPacket, byte.class, 0); - private Object getHeadRotationPacket() { - Object packet = Reflection.newInstance(headRotationPacket); - headRotationEntity.set(packet, entityId); - headRotationYaw.set(packet, headYaw); + ClientboundRotateHeadPacket packet = new ClientboundRotateHeadPacket(); + packet.entityId = entityId; + packet.yHeadRot = headYaw; return packet; } @@ -374,33 +372,6 @@ public class REntity { return new ClientboundSetEquipmentPacket(entityId, Collections.singletonList(Pair.of((EquipmentSlot) slot, CraftItemStack.asNMSCopy(stack)))); } - private static final Reflection.Field spawnType = Reflection.getField(ClientboundAddEntityPacket.class, net.minecraft.world.entity.EntityType.class, 0); - - private static Function entitySpawnPacketGenerator(Class spawnPacket, int posOffset) { - BountifulWrapper.UUIDSetter uuid = BountifulWrapper.impl.getUUIDSetter(spawnPacket); - Function packetGenerator = spawnPacketGenerator(spawnPacket, posOffset); - - return entity -> { - Object packet = packetGenerator.apply(entity); - uuid.set(packet, entity.uuid); - ResourceLocation key = CraftNamespacedKey.toMinecraft(entity.entityType.getKey()); - spawnType.set(packet, BuiltInRegistries.ENTITY_TYPE.get(key).get().value()); - return packet; - }; - } - - protected static Function spawnPacketGenerator(Class spawnPacket, int posOffset) { - Reflection.Field entityId = Reflection.getField(spawnPacket, int.class, 0); - BountifulWrapper.PositionSetter position = BountifulWrapper.impl.getPositionSetter(spawnPacket, posOffset); - - return entity -> { - Object packet = Reflection.newInstance(spawnPacket); - entityId.set(packet, entity.entityId); - position.set(packet, entity.x, entity.y, entity.z, entity.pitch, entity.yaw); - return packet; - }; - } - private byte rotToByte(float rot) { return (byte) ((int) (rot * 256.0F / 360.0F)); } @@ -408,4 +379,8 @@ public class REntity { private int calcVelocity(double value) { return (int) (Math.max(-3.9, Math.min(value, 3.9)) * 8000); } + + private static Optional formatDisplayName(String displayName) { + return displayName != null ? Optional.of(MutableComponent.create(PlainTextContents.create(displayName))) : Optional.empty(); + } } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RFallingBlockEntity.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RFallingBlockEntity.java index e1ab5cba..50226dfa 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RFallingBlockEntity.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RFallingBlockEntity.java @@ -20,7 +20,6 @@ package de.steamwar.entity; import de.steamwar.techhider.BlockIds; -import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; import org.bukkit.Location; diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RInteraction.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RInteraction.java index 58514280..7d27063e 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RInteraction.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RInteraction.java @@ -19,14 +19,13 @@ package de.steamwar.entity; -import de.steamwar.core.BountifulWrapper; import lombok.Getter; import lombok.Setter; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.network.syncher.EntityDataSerializers; import org.bukkit.Location; import org.bukkit.entity.EntityType; -import java.util.ArrayList; -import java.util.List; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -61,18 +60,13 @@ public class RInteraction extends REntity implements RInteractableEntity packetSink, BiConsumer>... dataSinkSinks) { - List keyValueData = new ArrayList<>(); + protected final void sendPacket(Consumer packetSink, BiConsumer... dataSinkSinks) { + EntityDataPacketBuilder builder = entityDataPacket(); boolean ignoreDefault = packetSink == updatePacketSink; - for (BiConsumer> dataSinkSink : dataSinkSinks) { - dataSinkSink.accept(ignoreDefault, (dataWatcher, value) -> { - keyValueData.add(dataWatcher); - keyValueData.add(value); - }); - } - if (!keyValueData.isEmpty()) { - packetSink.accept(getDataWatcherPacket(keyValueData.toArray())); + for (BiConsumer dataSinkSink : dataSinkSinks) { + dataSinkSink.accept(ignoreDefault, builder); } + if (!builder.isEmpty()) builder.send(packetSink); } public void setInteractionWidth(float interactionWidth) { @@ -80,11 +74,11 @@ public class RInteraction extends REntity implements RInteractableEntity interactionWidthWatcher = new EntityDataAccessor<>(8, EntityDataSerializers.FLOAT); - private void getInteractionWidthData(boolean ignoreDefault, BiConsumer dataSink) { + private void getInteractionWidthData(boolean ignoreDefault, EntityDataPacketBuilder dataSink) { if (ignoreDefault || interactionWidth != 1.0) { - dataSink.accept(interactionWidthWatcher, interactionWidth); + dataSink.add(interactionWidthWatcher, interactionWidth); } } @@ -93,11 +87,11 @@ public class RInteraction extends REntity implements RInteractableEntity interactionHeightWatcher = new EntityDataAccessor<>(9, EntityDataSerializers.FLOAT); - private void getInteractionHeightData(boolean ignoreDefault, BiConsumer dataSink) { + private void getInteractionHeightData(boolean ignoreDefault, EntityDataPacketBuilder dataSink) { if (ignoreDefault || interactionHeight != 1.0) { - dataSink.accept(interactionHeightWatcher, interactionHeight); + dataSink.add(interactionHeightWatcher, interactionHeight); } } @@ -106,11 +100,11 @@ public class RInteraction extends REntity implements RInteractableEntity responsiveWatcher = new EntityDataAccessor<>(10, EntityDataSerializers.BOOLEAN); - private void getResponsiveData(boolean ignoreDefault, BiConsumer dataSink) { + private void getResponsiveData(boolean ignoreDefault, EntityDataPacketBuilder dataSink) { if (ignoreDefault || !responsive) { - dataSink.accept(responsiveWatcher, responsive); + dataSink.add(responsiveWatcher, responsive); } } } \ No newline at end of file diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RItemDisplay.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RItemDisplay.java index e10d2ec9..21453516 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RItemDisplay.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RItemDisplay.java @@ -63,9 +63,9 @@ public class RItemDisplay extends RDisplay { private static final EntityDataAccessor itemStackWatcher = new EntityDataAccessor<>(23, EntityDataSerializers.ITEM_STACK); - private void getItemStack(boolean ignoreDefault, BiConsumer packetSink) { + private void getItemStack(boolean ignoreDefault, EntityDataPacketBuilder packetSink) { if (ignoreDefault || !itemStack.equals(DEFAULT_ITEM_STACK)) { - packetSink.accept(itemStackWatcher, CraftItemStack.asNMSCopy(itemStack)); + packetSink.add(itemStackWatcher, CraftItemStack.asNMSCopy(itemStack)); } } @@ -76,9 +76,9 @@ public class RItemDisplay extends RDisplay { sendPacket(updatePacketSink, this::getItemDisplayTransform); } - private void getItemDisplayTransform(boolean ignoreDefault, BiConsumer packetSink) { + private void getItemDisplayTransform(boolean ignoreDefault, EntityDataPacketBuilder packetSink) { if (ignoreDefault || itemDisplayTransform != ItemDisplay.ItemDisplayTransform.NONE) { - packetSink.accept(itemDisplayTransformWatcher, (byte) itemDisplayTransform.ordinal()); + packetSink.add(itemDisplayTransformWatcher, (byte) itemDisplayTransform.ordinal()); } } } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RPlayer.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RPlayer.java index daa9a648..cfb6304d 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RPlayer.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RPlayer.java @@ -21,7 +21,6 @@ package de.steamwar.entity; import com.mojang.authlib.GameProfile; import com.mojang.authlib.properties.Property; -import de.steamwar.core.BountifulWrapper; import de.steamwar.core.ProtocolWrapper; import de.steamwar.network.CoreNetworkHandler; import de.steamwar.network.NetworkSender; @@ -29,6 +28,8 @@ import de.steamwar.network.packets.common.PlayerSkinRequestPacket; import lombok.Getter; import lombok.Setter; import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.world.phys.Vec3; import org.bukkit.GameMode; import org.bukkit.Location; @@ -42,7 +43,7 @@ import java.util.function.Consumer; public class RPlayer extends REntity implements RInteractableEntity { - private static final Object skinPartsDataWatcher = BountifulWrapper.impl.getDataWatcherObject(17, Byte.class); + private static final EntityDataAccessor skinPartsDataWatcher = new EntityDataAccessor<>(17, EntityDataSerializers.BYTE); @Getter private final UUID actualUUID; @@ -85,7 +86,7 @@ public class RPlayer extends REntity implements RInteractableEntity { @Override void spawn(Consumer packetSink) { packetSink.accept(getNamedSpawnPacket()); - packetSink.accept(getDataWatcherPacket(skinPartsDataWatcher, (byte) 0x7F)); + entityDataPacket().add(skinPartsDataWatcher, (byte) 0x7F).send(packetSink); for (Map.Entry entry : itemSlots.entrySet()) { packetSink.accept(getEquipmentPacket(entry.getKey(), entry.getValue())); diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RTextDisplay.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RTextDisplay.java index b6db865f..f30fbfd8 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RTextDisplay.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/entity/RTextDisplay.java @@ -77,9 +77,9 @@ public class RTextDisplay extends RDisplay { private static final EntityDataAccessor textWatcher = new EntityDataAccessor<>(23, EntityDataSerializers.COMPONENT); - private void getText(boolean ignoreDefault, BiConsumer packetSink) { + private void getText(boolean ignoreDefault, EntityDataPacketBuilder packetSink) { if (ignoreDefault || !text.isEmpty()) { - packetSink.accept(textWatcher, MutableComponent.create(PlainTextContents.create(text))); + packetSink.add(textWatcher, MutableComponent.create(PlainTextContents.create(text))); } } @@ -90,9 +90,9 @@ public class RTextDisplay extends RDisplay { private static final EntityDataAccessor lineWidthWatcher = new EntityDataAccessor<>(24, EntityDataSerializers.INT); - private void getLineWidth(boolean ignoreDefault, BiConsumer packetSink) { + private void getLineWidth(boolean ignoreDefault, EntityDataPacketBuilder packetSink) { if (ignoreDefault || lineWidth != 200) { - packetSink.accept(lineWidthWatcher, lineWidth); + packetSink.add(lineWidthWatcher, lineWidth); } } @@ -103,9 +103,9 @@ public class RTextDisplay extends RDisplay { private static final EntityDataAccessor textOpacityWatcher = new EntityDataAccessor<>(26, EntityDataSerializers.BYTE); - private void getTextOpacity(boolean ignoreDefault, BiConsumer packetSink) { + private void getTextOpacity(boolean ignoreDefault, EntityDataPacketBuilder packetSink) { if (ignoreDefault || textOpacity != (byte) -1) { - packetSink.accept(textOpacityWatcher, textOpacity); + packetSink.add(textOpacityWatcher, textOpacity); } } @@ -126,9 +126,9 @@ public class RTextDisplay extends RDisplay { private static final EntityDataAccessor backgroundColorWatcher = new EntityDataAccessor<>(25, EntityDataSerializers.INT); - private void getBackgroundColor(boolean ignoreDefault, BiConsumer packetSink) { + private void getBackgroundColor(boolean ignoreDefault, EntityDataPacketBuilder packetSink) { if (ignoreDefault || backgroundColor != null) { - packetSink.accept(backgroundColorWatcher, backgroundColor); + packetSink.add(backgroundColorWatcher, backgroundColor); } } @@ -146,7 +146,7 @@ public class RTextDisplay extends RDisplay { private static final EntityDataAccessor textStatusWatcher = new EntityDataAccessor<>(27, EntityDataSerializers.BYTE); - private void getTextStatus(boolean ignoreDefault, BiConsumer packetSink) { + private void getTextStatus(boolean ignoreDefault, EntityDataPacketBuilder packetSink) { byte status = 0; if (shadowed) { @@ -167,7 +167,7 @@ public class RTextDisplay extends RDisplay { } if (ignoreDefault || status != 0) { - packetSink.accept(textStatusWatcher, status); + packetSink.add(textStatusWatcher, status); } } } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java index 3d7fa97c..bd21f357 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ChunkHider.java @@ -19,10 +19,8 @@ package de.steamwar.techhider; -import de.steamwar.Reflection; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; -import net.minecraft.core.SectionPos; import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData; import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; import net.minecraft.util.SimpleBitStorage; @@ -35,13 +33,8 @@ import java.util.List; import java.util.function.UnaryOperator; public class ChunkHider { - private static final UnaryOperator chunkPacketShallowCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkWithLightPacket.class); - private static final UnaryOperator chunkDataShallowCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkPacketData.class); - - private static final Reflection.Field levelChunkPacketDataField = Reflection.getField(ClientboundLevelChunkWithLightPacket.class, ClientboundLevelChunkPacketData.class, 0); - - private static final Reflection.Field chunkBlockDataField = Reflection.getField(ClientboundLevelChunkPacketData.class, byte[].class, 0); - private static final Reflection.Field chunkBlockEntitiesDataField = Reflection.getField(ClientboundLevelChunkPacketData.class, List.class, 0); + private static final UnaryOperator chunkPacketShallowCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkWithLightPacket.class, ClientboundLevelChunkWithLightPacket::new); + private static final UnaryOperator chunkDataShallowCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkPacketData.class, ClientboundLevelChunkPacketData::new); private final int SECTION_SPAN_SIZE = 16; private final byte BIT_PER_BLOCK_INDIRECTION_LIMIT = 8; @@ -178,8 +171,8 @@ public class ChunkHider { byte[] data = new byte[out.readableBytes()]; out.readBytes(data); - List blockEntities = chunkBlockEntitiesDataField.get(chunkData); - List filteredBlockEntities = filterBlockEntities(player, blockEntities, chunkX, chunkZ); + List blockEntities = chunkData.blockEntitiesData; + List filteredBlockEntities = filterBlockEntities(player, blockEntities, chunkX, chunkZ); return buildNewChunkPacket(packet, data, filteredBlockEntities); @@ -237,33 +230,24 @@ public class ChunkHider { return reEncodedData.getRaw(); } - private ClientboundLevelChunkWithLightPacket buildNewChunkPacket(ClientboundLevelChunkWithLightPacket originalPacket, byte[] newBlockDataBuffer, List newBlockEntities) { - ClientboundLevelChunkWithLightPacket clonedPacket = (ClientboundLevelChunkWithLightPacket) chunkPacketShallowCloner.apply(originalPacket); - ClientboundLevelChunkPacketData clonedPacketChunkData = (ClientboundLevelChunkPacketData) chunkDataShallowCloner.apply(originalPacket.getChunkData()); + private ClientboundLevelChunkWithLightPacket buildNewChunkPacket(ClientboundLevelChunkWithLightPacket originalPacket, byte[] newBlockDataBuffer, List newBlockEntities) { + ClientboundLevelChunkWithLightPacket clonedPacket = chunkPacketShallowCloner.apply(originalPacket); + ClientboundLevelChunkPacketData clonedPacketChunkData = chunkDataShallowCloner.apply(originalPacket.getChunkData()); - chunkBlockDataField.set(clonedPacketChunkData, newBlockDataBuffer); - chunkBlockEntitiesDataField.set(clonedPacketChunkData, newBlockEntities); - - levelChunkPacketDataField.set(clonedPacket, clonedPacketChunkData); + clonedPacketChunkData.buffer = newBlockDataBuffer; + clonedPacketChunkData.blockEntitiesData = newBlockEntities; + clonedPacket.chunkData = clonedPacketChunkData; return clonedPacket; } - - private static final Class blockEntitiyInfoClass = Reflection.getClass("net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData$BlockEntityInfo"); - - private static final Reflection.Field blockEntityInfoTypeField = Reflection.getField(blockEntitiyInfoClass, BlockEntityType.class, 0); - private static final Reflection.Field packedXZField = Reflection.getField(blockEntitiyInfoClass, int.class, 0); - private static final Reflection.Field yField = Reflection.getField(blockEntitiyInfoClass, int.class, 1); - - private List filterBlockEntities(Player player, List blockEntities, int chunkX, int chunkZ) { + private List filterBlockEntities(Player player, List blockEntities, int chunkX, int chunkZ) { int fourBitBitmask = 0b0000_1111; return blockEntities.stream() .filter((blockEntityInfo) -> { - BlockEntityType type = blockEntityInfoTypeField.get(blockEntityInfo); - - int packedXZ = packedXZField.get(blockEntityInfo); + BlockEntityType type = blockEntityInfo.type; + int packedXZ = blockEntityInfo.packedXZ; int localX = (packedXZ >> 4) & fourBitBitmask; int localZ = packedXZ & fourBitBitmask; @@ -271,7 +255,7 @@ public class ChunkHider { int worldX = (chunkX * SECTION_SPAN_SIZE) + localX; int worldZ = (chunkZ * SECTION_SPAN_SIZE) + localZ; - int worldY = yField.get(blockEntityInfo); + int worldY = blockEntityInfo.y; return accessPrivilegeProvider.isPlayerPrivilegedToAccessPosition(player, worldX, worldY, worldZ) && accessPrivilegeProvider.isPlayerPrivilegedToAccessBlockEntity(player, worldX, worldY, worldZ, type); }).toList(); diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ProtocolUtils.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ProtocolUtils.java index e71b522a..70d86008 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ProtocolUtils.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/ProtocolUtils.java @@ -19,64 +19,37 @@ package de.steamwar.techhider; -import com.google.common.primitives.Bytes; -import de.steamwar.Reflection; import io.netty.buffer.ByteBuf; -import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; import java.util.function.BiConsumer; -import java.util.function.BiFunction; +import java.util.function.Supplier; import java.util.function.UnaryOperator; public class ProtocolUtils { private ProtocolUtils() { } - @Deprecated - public static BiFunction, Object> arrayCloneGenerator(Class elementClass) { - return (array, worker) -> { - int length = Array.getLength(array); - Object result = Array.newInstance(elementClass, length); - - for (int i = 0; i < length; i++) { - Array.set(result, i, worker.apply(Array.get(array, i))); - } - - return result; - }; - } - - public static UnaryOperator shallowCloneGenerator(Class clazz) { - BiConsumer filler = shallowFill(clazz); + public static UnaryOperator shallowCloneGenerator(Class clazz, Supplier supplier) { + BiConsumer filler = shallowFill(clazz); return source -> { - Object clone = Reflection.newInstance(clazz); + T clone = supplier.get(); filler.accept(source, clone); return clone; }; } - public static UnaryOperator shallowTypedCloneGenerator(Class clazz) { - BiConsumer filler = shallowFill(clazz); - - return source -> { - Object clone = Reflection.newInstance(clazz); - filler.accept(source, clone); - return (T) clone; - }; - } - - private static BiConsumer shallowFill(Class clazz) { + private static BiConsumer shallowFill(Class clazz) { if (clazz == null) { return (source, clone) -> { }; } - BiConsumer superFiller = shallowFill(clazz.getSuperclass()); + BiConsumer superFiller = shallowFill(clazz.getSuperclass()); Field[] fds = clazz.getDeclaredFields(); List fields = new ArrayList<>(); @@ -106,25 +79,6 @@ public class ProtocolUtils { return chunk; } - @Deprecated - public static int readVarInt(byte[] array, int startPos) { - int numRead = 0; - int result = 0; - byte read; - do { - read = array[startPos + numRead]; - int value = (read & 0b01111111); - result |= (value << (7 * numRead)); - - numRead++; - if (numRead > 5) { - break; - } - } while ((read & 0b10000000) != 0); - - return result; - } - public static int readVarInt(ByteBuf buf) { int numRead = 0; int result = 0; @@ -167,53 +121,4 @@ public class ProtocolUtils { writeVarInt(buf, varInt); } } - - @Deprecated - public static int readVarIntLength(byte[] array, int startPos) { - int numRead = 0; - byte read; - do { - read = array[startPos + numRead]; - numRead++; - if (numRead > 5) { - break; - } - } while ((read & 0b10000000) != 0); - - return numRead; - } - - @Deprecated - public static byte[] writeVarInt(int value) { - List buffer = new ArrayList<>(5); - do { - byte temp = (byte) (value & 0b01111111); - // Note: >>> means that the sign bit is shifted with the rest of the number rather than being left alone - value >>>= 7; - if (value != 0) { - temp |= 0b10000000; - } - buffer.add(temp); - } while (value != 0); - return Bytes.toArray(buffer); - } - - @Deprecated - public static class ChunkPos { - final int x; - final int z; - - public ChunkPos(int x, int z) { - this.x = x; - this.z = z; - } - - public final int x() { - return x; - } - - public final int z() { - return z; - } - } } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java index 1539f16d..0fef6f1e 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/TechHider.java @@ -20,7 +20,6 @@ package de.steamwar.techhider; import com.comphenix.tinyprotocol.TinyProtocol; -import de.steamwar.Reflection; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.shorts.ShortArraySet; @@ -364,24 +363,16 @@ public class TechHider { }; } - private final Reflection.Field moveEntityPacketEntityIdField = Reflection.getField(ClientboundMoveEntityPacket.class, int.class, 0); - private Packet processMoveEntityPacket(Player player, ClientboundMoveEntityPacket packet) { - int entityId = moveEntityPacketEntityIdField.get(packet); - - if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, entityId)) { + if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, packet.entityId)) { return packet; } else { return null; } } - private final Reflection.Field rotateHeadPacketEntityIdField = Reflection.getField(ClientboundRotateHeadPacket.class, int.class, 0); - private Packet processRotateHeadPacket(Player player, ClientboundRotateHeadPacket packet) { - int entityId = rotateHeadPacketEntityIdField.get(packet); - - if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, entityId)) { + if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, packet.entityId)) { return packet; } else { return null; @@ -463,14 +454,10 @@ public class TechHider { } } - private final Reflection.Field sectionPosField = Reflection.getField(ClientboundSectionBlocksUpdatePacket.class, SectionPos.class, 0); - private final Reflection.Field oldPosField = Reflection.getField(ClientboundSectionBlocksUpdatePacket.class, short[].class, 0); - private final Reflection.Field oldStatesField = Reflection.getField(ClientboundSectionBlocksUpdatePacket.class, BlockState[].class, 0); - private ClientboundSectionBlocksUpdatePacket processSectionUpdate(Player p, ClientboundSectionBlocksUpdatePacket packet) { - SectionPos sectionPos = sectionPosField.get(packet); - short[] oldPos = oldPosField.get(packet); - BlockState[] oldStates = oldStatesField.get(packet); + SectionPos sectionPos = packet.sectionPos; + short[] oldPos = packet.positions; + BlockState[] oldStates = packet.states; List filteredPos = new ArrayList<>(oldPos.length); List filteredStates = new ArrayList<>(oldStates.length); diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ChunkHider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ChunkHider.java index c3df8e6f..9aaa9b40 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ChunkHider.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ChunkHider.java @@ -19,22 +19,18 @@ package de.steamwar.techhider.legacy; -import de.steamwar.Reflection; import de.steamwar.techhider.ProtocolUtils; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; -import net.minecraft.core.Registry; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData; import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; -import net.minecraft.resources.ResourceLocation; import net.minecraft.util.SimpleBitStorage; import net.minecraft.world.level.block.entity.BlockEntityType; import org.bukkit.entity.Player; import java.util.Collections; -import java.util.List; import java.util.Set; import java.util.function.BiFunction; import java.util.function.UnaryOperator; @@ -44,35 +40,26 @@ import java.util.stream.Collectors; public class ChunkHider { public static final ChunkHider impl = new ChunkHider(); - public Class mapChunkPacket() { - return ClientboundLevelChunkWithLightPacket.class; - } + private static final UnaryOperator chunkPacketCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkWithLightPacket.class, ClientboundLevelChunkWithLightPacket::new); + private static final UnaryOperator chunkDataCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkPacketData.class, ClientboundLevelChunkPacketData::new); - private static final UnaryOperator chunkPacketCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkWithLightPacket.class); - private static final UnaryOperator chunkDataCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkPacketData.class); - - private static final Reflection.Field chunkXField = Reflection.getField(ClientboundLevelChunkWithLightPacket.class, int.class, 0); - private static final Reflection.Field chunkZField = Reflection.getField(ClientboundLevelChunkWithLightPacket.class, int.class, 1); - private static final Reflection.Field chunkData = Reflection.getField(ClientboundLevelChunkWithLightPacket.class, ClientboundLevelChunkPacketData.class, 0); - - private static final Reflection.Field dataField = Reflection.getField(ClientboundLevelChunkPacketData.class, byte[].class, 0); - private static final Reflection.Field tileEntities = Reflection.getField(ClientboundLevelChunkPacketData.class, List.class, 0); - - public BiFunction chunkHiderGenerator(TechHider techHider) { + public BiFunction chunkHiderGenerator(TechHider techHider) { return (p, packet) -> { - int chunkX = chunkXField.get(packet); - int chunkZ = chunkZField.get(packet); + int chunkX = packet.getX(); + int chunkZ = packet.getZ(); if (techHider.getLocationEvaluator().skipChunk(p, chunkX, chunkZ)) { return packet; } packet = chunkPacketCloner.apply(packet); - Object dataWrapper = chunkDataCloner.apply(chunkData.get(packet)); + ClientboundLevelChunkPacketData dataWrapper = chunkDataCloner.apply(packet.getChunkData()); Set hiddenBlockEntities = techHider.getHiddenBlockEntities(); - tileEntities.set(dataWrapper, ((List) tileEntities.get(dataWrapper)).stream().filter(te -> tileEntityVisible(hiddenBlockEntities, te)).collect(Collectors.toList())); + dataWrapper.blockEntitiesData = dataWrapper.blockEntitiesData.stream() + .filter(te -> tileEntityVisible(hiddenBlockEntities, te)) + .collect(Collectors.toList()); - ByteBuf in = Unpooled.wrappedBuffer(dataField.get(dataWrapper)); + ByteBuf in = Unpooled.wrappedBuffer(dataWrapper.buffer); ByteBuf out = Unpooled.buffer(in.readableBytes() + 64); for (int yOffset = p.getWorld().getMinHeight(); yOffset < p.getWorld().getMaxHeight(); yOffset += 16) { SectionHider section = new SectionHider(p, techHider, in, out, chunkX, yOffset / 16, chunkZ); @@ -88,21 +75,16 @@ public class ChunkHider { byte[] data = new byte[out.readableBytes()]; out.readBytes(data); - dataField.set(dataWrapper, data); + dataWrapper.buffer = data; - chunkData.set(packet, dataWrapper); + packet.chunkData = dataWrapper; return packet; }; } - private static final Registry> registry = Reflection.getField(BuiltInRegistries.class, "BLOCK_ENTITY_TYPE", Registry.class).get(null); - private static final Reflection.Method getKey = Reflection.getTypedMethod(Reflection.getClass("net.minecraft.core.Registry"), "getKey", ResourceLocation.class, Object.class); - public static final Class tileEntity = Reflection.getClass("net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData$BlockEntityInfo"); - protected static final Reflection.Field entityType = Reflection.getField(tileEntity, BlockEntityType.class, 0); - - protected boolean tileEntityVisible(Set hiddenBlockEntities, Object tile) { - BlockEntityType type = entityType.get(tile); - String path = ((ResourceLocation) getKey.invoke(registry, type)).getPath(); + protected boolean tileEntityVisible(Set hiddenBlockEntities, ClientboundLevelChunkPacketData.BlockEntityInfo tile) { + BlockEntityType type = tile.type; + String path = BuiltInRegistries.BLOCK_ENTITY_TYPE.getKey(type).getPath(); return !hiddenBlockEntities.contains(path); } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ProtocolWrapper.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ProtocolWrapper.java index 00e0dc31..cb7b8d05 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ProtocolWrapper.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/ProtocolWrapper.java @@ -19,10 +19,8 @@ package de.steamwar.techhider.legacy; -import de.steamwar.Reflection; import net.minecraft.core.SectionPos; -import net.minecraft.world.level.block.entity.BlockEntityType; -import net.minecraft.world.level.block.entity.SignBlockEntity; +import net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket; import net.minecraft.world.level.block.state.BlockState; import org.bukkit.entity.Player; @@ -33,24 +31,20 @@ import java.util.function.BiFunction; public class ProtocolWrapper { public static final ProtocolWrapper impl = new ProtocolWrapper(); - private static final Reflection.Field multiBlockChangeChunk = Reflection.getField(TechHider.multiBlockChangePacket, SectionPos.class, 0); - private static final Reflection.Field multiBlockChangePos = Reflection.getField(TechHider.multiBlockChangePacket, short[].class, 0); - private static final Reflection.Field multiBlockChangeBlocks = Reflection.getField(TechHider.multiBlockChangePacket, BlockState[].class, 0); - - public BiFunction multiBlockChangeGenerator(TechHider techHider) { + public BiFunction multiBlockChangeGenerator(TechHider techHider) { return (p, packet) -> { TechHider.LocationEvaluator locationEvaluator = techHider.getLocationEvaluator(); - Object chunkCoords = multiBlockChangeChunk.get(packet); - int chunkX = TechHider.blockPositionX.get(chunkCoords); - int chunkY = TechHider.blockPositionY.get(chunkCoords); - int chunkZ = TechHider.blockPositionZ.get(chunkCoords); + SectionPos chunkCoords = packet.sectionPos; + int chunkX = chunkCoords.getX(); + int chunkY = chunkCoords.getY(); + int chunkZ = chunkCoords.getZ(); if (locationEvaluator.skipChunkSection(p, chunkX, chunkY, chunkZ)) { return packet; } packet = TechHider.multiBlockChangeCloner.apply(packet); - final short[] oldPos = multiBlockChangePos.get(packet); - final BlockState[] oldBlocks = multiBlockChangeBlocks.get(packet); + final short[] oldPos = packet.positions; + final BlockState[] oldBlocks = packet.states; ArrayList poss = new ArrayList<>(oldPos.length); ArrayList blocks = new ArrayList<>(oldPos.length); for (int i = 0; i < oldPos.length; i++) { @@ -77,16 +71,9 @@ public class ProtocolWrapper { newPos[i] = poss.get(i); } - multiBlockChangePos.set(packet, newPos); - multiBlockChangeBlocks.set(packet, blocks.toArray(new BlockState[0])); + packet.positions = newPos; + packet.states = blocks.toArray(BlockState[]::new); return packet; }; } - - private static final Reflection.Field tileEntityType = Reflection.getField(TechHider.tileEntityDataPacket, BlockEntityType.class, 0); - private static final BlockEntityType signType = Reflection.getField(BlockEntityType.class, BlockEntityType.class, 0, SignBlockEntity.class).get(null); - - public boolean unfilteredTileEntityDataAction(Object packet) { - return tileEntityType.get(packet) != signType; - } } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/TechHider.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/TechHider.java index 25b3f215..dfa0a5f3 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/TechHider.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/techhider/legacy/TechHider.java @@ -20,7 +20,6 @@ package de.steamwar.techhider.legacy; import com.comphenix.tinyprotocol.TinyProtocol; -import de.steamwar.Reflection; import de.steamwar.techhider.BlockIds; import de.steamwar.techhider.ProtocolUtils; import lombok.Getter; @@ -28,6 +27,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.Vec3i; import net.minecraft.network.protocol.game.*; import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; import org.bukkit.Material; import org.bukkit.craftbukkit.util.CraftMagicNumbers; @@ -37,7 +37,6 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.function.BiFunction; -import java.util.function.Function; import java.util.function.UnaryOperator; import java.util.stream.Collectors; @@ -45,10 +44,6 @@ import java.util.stream.Collectors; public class TechHider { public static final Class blockPosition = BlockPos.class; - private static final Class baseBlockPosition = Vec3i.class; - public static final Reflection.Field blockPositionX = Reflection.getField(baseBlockPosition, int.class, 0); - public static final Reflection.Field blockPositionY = Reflection.getField(baseBlockPosition, int.class, 1); - public static final Reflection.Field blockPositionZ = Reflection.getField(baseBlockPosition, int.class, 2); public static final Class iBlockData = BlockState.class; public static final Class block = Block.class; @@ -79,11 +74,11 @@ public class TechHider { this.obfuscationTarget = CraftMagicNumbers.getBlock(obfuscationTarget).defaultBlockState(); this.obfuscationTargetId = BlockIds.impl.materialToId(obfuscationTarget); - techhiders.put(blockActionPacket, this::blockActionHider); - techhiders.put(blockChangePacket, this::blockChangeHider); - techhiders.put(tileEntityDataPacket, this::tileEntityDataHider); - techhiders.put(multiBlockChangePacket, ProtocolWrapper.impl.multiBlockChangeGenerator(this)); - techhiders.put(ChunkHider.impl.mapChunkPacket(), ChunkHider.impl.chunkHiderGenerator(this)); + techhiders.put(ClientboundBlockEventPacket.class, (player, o) -> this.blockActionHider(player, (ClientboundBlockEventPacket) o)); + techhiders.put(ClientboundBlockUpdatePacket.class, (player, o) -> this.blockChangeHider(player, (ClientboundBlockUpdatePacket) o)); + techhiders.put(ClientboundBlockEntityDataPacket.class, (player, o) -> this.tileEntityDataHider(player, (ClientboundBlockEntityDataPacket) o)); + techhiders.put(ClientboundSectionBlocksUpdatePacket.class, (BiFunction) ProtocolWrapper.impl.multiBlockChangeGenerator(this)); + techhiders.put(ClientboundLevelChunkWithLightPacket.class, (BiFunction) ChunkHider.impl.chunkHiderGenerator(this)); techhiders.put(ServerboundUseItemOnPacket.class, (p, packet) -> locationEvaluator.suppressInteractions(p) ? null : packet); techhiders.put(ServerboundInteractPacket.class, (p, packet) -> locationEvaluator.suppressInteractions(p) ? null : packet); @@ -97,53 +92,43 @@ public class TechHider { techhiders.forEach(TinyProtocol.instance::removeFilter); } - public static final Class multiBlockChangePacket = ClientboundSectionBlocksUpdatePacket.class; - public static final UnaryOperator multiBlockChangeCloner = ProtocolUtils.shallowCloneGenerator(TechHider.multiBlockChangePacket); + public static final UnaryOperator multiBlockChangeCloner = ProtocolUtils.shallowCloneGenerator(ClientboundSectionBlocksUpdatePacket.class, ClientboundSectionBlocksUpdatePacket::new); - private static final Class blockChangePacket = ClientboundBlockUpdatePacket.class; - private static final Function blockChangeCloner = ProtocolUtils.shallowCloneGenerator(blockChangePacket); - private static final Reflection.Field blockChangePosition = Reflection.getField(blockChangePacket, blockPosition, 0); - private static final Reflection.Field blockChangeBlockData = Reflection.getField(blockChangePacket, iBlockData, 0); + private static final UnaryOperator blockChangeCloner = ProtocolUtils.shallowCloneGenerator(ClientboundBlockUpdatePacket.class, ClientboundBlockUpdatePacket::new); - private Object blockChangeHider(Player p, Object packet) { - switch (locationEvaluator.checkBlockPos(p, blockChangePosition.get(packet))) { + private ClientboundBlockUpdatePacket blockChangeHider(Player p, ClientboundBlockUpdatePacket packet) { + switch (locationEvaluator.checkBlockPos(p, packet.getPos())) { case SKIP: return packet; case CHECK: - if (!iBlockDataHidden((BlockState) blockChangeBlockData.get(packet))) { + if (!iBlockDataHidden(packet.blockState)) { return packet; } case HIDE: packet = blockChangeCloner.apply(packet); - blockChangeBlockData.set(packet, obfuscationTarget); + packet.blockState = (BlockState) obfuscationTarget; return packet; case HIDE_AIR: default: packet = blockChangeCloner.apply(packet); - blockChangeBlockData.set(packet, AIR); + packet.blockState = (BlockState) AIR; return packet; } } - private static final Class blockActionPacket = ClientboundBlockEventPacket.class; - private static final Reflection.Field blockActionPosition = Reflection.getField(blockActionPacket, blockPosition, 0); - - private Object blockActionHider(Player p, Object packet) { - if (locationEvaluator.checkBlockPos(p, blockActionPosition.get(packet)) == State.SKIP) { + private Object blockActionHider(Player p, ClientboundBlockEventPacket packet) { + if (locationEvaluator.checkBlockPos(p, packet.getPos()) == State.SKIP) { return packet; } return null; } - public static final Class tileEntityDataPacket = ClientboundBlockEntityDataPacket.class; - private static final Reflection.Field tileEntityDataPosition = Reflection.getField(tileEntityDataPacket, blockPosition, 0); - - private Object tileEntityDataHider(Player p, Object packet) { - switch (locationEvaluator.checkBlockPos(p, tileEntityDataPosition.get(packet))) { + private ClientboundBlockEntityDataPacket tileEntityDataHider(Player p, ClientboundBlockEntityDataPacket packet) { + switch (locationEvaluator.checkBlockPos(p, packet.getPos())) { case SKIP: return packet; case CHECK: - if (ProtocolWrapper.impl.unfilteredTileEntityDataAction(packet)) { + if (packet.getType() != BlockEntityType.SIGN) { return packet; } default: @@ -173,8 +158,8 @@ public class TechHider { return skipChunkSection(player, ProtocolUtils.posToChunk(x), ProtocolUtils.posToChunk(y), ProtocolUtils.posToChunk(z)) ? State.SKIP : State.CHECK; } - default State checkBlockPos(Player player, Object pos) { - return check(player, blockPositionX.get(pos), blockPositionY.get(pos), blockPositionZ.get(pos)); + default State checkBlockPos(Player player, Vec3i pos) { + return check(player, pos.getX(), pos.getY(), pos.getZ()); } default boolean blockPrecise(Player player, int x, int y, int z) { diff --git a/SpigotCore/SpigotCore_Main/src/net/wesjd/anvilgui/AnvilGUI.java b/SpigotCore/SpigotCore_Main/src/net/wesjd/anvilgui/AnvilGUI.java new file mode 100644 index 00000000..d388dc36 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/net/wesjd/anvilgui/AnvilGUI.java @@ -0,0 +1,890 @@ +package net.wesjd.anvilgui; + +import lombok.Getter; +import lombok.NonNull; +import net.md_5.bungee.api.chat.BaseComponent; +import net.wesjd.anvilgui.version.VersionWrapper; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.*; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.plugin.Plugin; + +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.logging.Level; + +/** + * An anvil gui, used for gathering a user's input + * + * @author Wesley Smith + * @since 1.0 + */ +public class AnvilGUI { + + /** + * The local {@link VersionWrapper} object for the server's version + */ + private static final VersionWrapper WRAPPER = VersionWrapper.INSTANCE; + + /** + * The variable containing an item with air. Used when the item would be null. + * To keep the heap clean, this object only gets iniziaised once + */ + private static final ItemStack AIR = new ItemStack(Material.AIR); + /** + * If the given ItemStack is null, return an air ItemStack, otherwise return the given ItemStack + * + * @param stack The ItemStack to check + * @return air or the given ItemStack + */ + private static ItemStack itemNotNull(ItemStack stack) { + return stack == null ? AIR : stack; + } + + /** + * The {@link Plugin} that this anvil GUI is associated with + */ + private final Plugin plugin; + /** + * The player who has the GUI open + */ + private final Player player; + /** + * An {@link Executor} that executes tasks on the main server thread + */ + private final Executor mainThreadExecutor; + /** + * The title of the anvil inventory + */ + private final Object titleComponent; + /** + * The initial contents of the inventory + */ + private final ItemStack[] initialContents; + /** + * A state that decides where the anvil GUI is able to get closed by the user + */ + private final boolean preventClose; + + /** + * A set of slot numbers that are permitted to be interacted with by the user. An interactable + * slot is one that is able to be minipulated by the player, i.e. clicking and picking up an item, + * placing in a new one, etc. + */ + private final Set interactableSlots; + + /** An {@link Consumer} that is called when the anvil GUI is close */ + private final Consumer closeListener; + /** A flag that decides whether the async click handler can be run concurrently */ + private final boolean concurrentClickHandlerExecution; + /** An {@link BiFunction} that is called when a slot is clicked */ + private final ClickHandler clickHandler; + + /** + * The container id of the inventory, used for NMS methods + */ + private int containerId; + + /** + * The inventory that is used on the Bukkit side of things + */ + @Getter + private Inventory inventory; + /** + * The listener holder class + */ + private final ListenUp listener = new ListenUp(); + + /** + * Represents the state of the inventory being open + */ + private boolean open; + + /** + * The actual container backing the Anvil GUI + */ + private VersionWrapper.AnvilContainerWrapper container; + + /** + * Create an AnvilGUI + * + * @param plugin A {@link org.bukkit.plugin.java.JavaPlugin} instance + * @param player The {@link Player} to open the inventory for + * @param mainThreadExecutor An {@link Executor} that executes on the main server thread + * @param titleComponent What to have the text already set to + * @param initialContents The initial contents of the inventory + * @param preventClose Whether to prevent the inventory from closing + * @param closeListener A {@link Consumer} when the inventory closes + * @param concurrentClickHandlerExecution Flag to allow concurrent execution of the click handler + * @param clickHandler A {@link ClickHandler} that is called when the player clicks a slot + */ + private AnvilGUI( + Plugin plugin, + Player player, + Executor mainThreadExecutor, + Object titleComponent, + ItemStack[] initialContents, + boolean preventClose, + Set interactableSlots, + Consumer closeListener, + boolean concurrentClickHandlerExecution, + ClickHandler clickHandler) { + this.plugin = plugin; + this.player = player; + this.mainThreadExecutor = mainThreadExecutor; + this.titleComponent = titleComponent; + this.initialContents = initialContents; + this.preventClose = preventClose; + this.interactableSlots = Collections.unmodifiableSet(interactableSlots); + this.closeListener = closeListener; + this.concurrentClickHandlerExecution = concurrentClickHandlerExecution; + this.clickHandler = clickHandler; + } + + /** + * Opens the anvil GUI + */ + private void openInventory() { + Bukkit.getPluginManager().registerEvents(listener, plugin); + + container = WRAPPER.newContainerAnvil(player, titleComponent); + + inventory = container.getBukkitInventory(); + // We need to use setItem instead of setContents because a Minecraft ContainerAnvil + // contains two separate inventories: the result inventory and the ingredients inventory. + // The setContents method only updates the ingredients inventory unfortunately, + // but setItem handles the index going into the result inventory. + for (int i = 0; i < initialContents.length; i++) { + inventory.setItem(i, initialContents[i]); + } + + containerId = WRAPPER.getNextContainerId(player, container); + WRAPPER.handleInventoryCloseEvent(player); + WRAPPER.sendPacketOpenWindow(player, containerId, titleComponent); + WRAPPER.setActiveContainer(player, container); + WRAPPER.setActiveContainerId(container, containerId); + WRAPPER.addActiveContainerSlotListener(container, player); + + open = true; + } + + /** + * Closes the inventory if it's open. + */ + public void closeInventory() { + closeInventory(true); + } + + /** + * Closes the inventory if it's open, only sending the close inventory packets if the arg is true + * + * @param sendClosePacket Whether to send the close inventory event, packet, etc + */ + private void closeInventory(boolean sendClosePacket) { + if (!open) { + return; + } + + open = false; + + HandlerList.unregisterAll(listener); + + inventory = container.getBukkitInventory(); + // We need to use setItem instead of setContents because a Minecraft ContainerAnvil + // contains two separate inventories: the result inventory and the ingredients inventory. + // The setContents method only updates the ingredients inventory unfortunately, + // but setItem handles the index going into the result inventory. + for (int i = 0; i < initialContents.length; i++) { + inventory.setItem(i, new ItemStack(Material.AIR)); + } + + if (sendClosePacket) { + WRAPPER.handleInventoryCloseEvent(player); + WRAPPER.setActiveContainerDefault(player); + WRAPPER.sendPacketCloseWindow(player, containerId); + } + + if (closeListener != null) { + closeListener.accept(StateSnapshot.fromAnvilGUI(this)); + } + } + + /** + * Updates the title of the AnvilGUI to the new one. + * + * @param literalTitle The title to use as literal text + * @param preserveRenameText Whether to preserve the entered rename text + * @throws IllegalArgumentException when literalTitle is null + * @see Builder#title(String) + */ + public void setTitle(@NonNull String literalTitle, boolean preserveRenameText) { + setTitle(WRAPPER.literalChatComponent(literalTitle), preserveRenameText); + } + + /** + * Updates the title of the AnvilGUI to the new one. + * + * @param json The json used to parse into a rich chat component + * @param preserveRenameText Whether to preserve the entered rename text + * @throws IllegalArgumentException when json is null + * @see Builder#jsonTitle(String) + */ + public void setJsonTitle(@NonNull String json, boolean preserveRenameText) { + setTitle(WRAPPER.jsonChatComponent(json), preserveRenameText); + } + + /** + * Updates the title of the AnvilGUI to the new one. + * + * @param title The title as a NMS ChatComponent + * @param preserveRenameText Whether to preserve the entered rename text + */ + private void setTitle(Object title, boolean preserveRenameText) { + String renameText = container.getRenameText(); + WRAPPER.sendPacketOpenWindow(player, containerId, title); + if (preserveRenameText) { + // The renameText field is marked as @Nullable in newer versions + container.setRenameText(renameText == null ? "" : renameText); + } + } + + /** + * Simply holds the listeners for the GUI + */ + private class ListenUp implements Listener { + + /** + * Boolean storing the running status of the latest click handler to prevent double execution. + * All accesses to this boolean will be from the main server thread, except for the rare event + * that the plugin is disabled and the mainThreadExecutor throws an exception + */ + private boolean clickHandlerRunning = false; + + @EventHandler + public void onInventoryClick(InventoryClickEvent event) { + if (!event.getInventory().equals(inventory)) { + return; + } + + final int rawSlot = event.getRawSlot(); + // ignore items dropped outside the window + if (rawSlot == -999) return; + + final Player clicker = (Player) event.getWhoClicked(); + final Inventory clickedInventory = event.getClickedInventory(); + + if (clickedInventory != null) { + if (clickedInventory.equals(clicker.getInventory())) { + // prevent players from merging items from the anvil inventory + if (event.getClick().equals(ClickType.DOUBLE_CLICK)) { + event.setCancelled(true); + return; + } + // prevent shift moving items from players inv to the anvil inventory + if (event.isShiftClick()) { + event.setCancelled(true); + return; + } + } + // prevent players from swapping items in the anvil gui + if ((event.getCursor() != null && event.getCursor().getType() != Material.AIR) + && !interactableSlots.contains(rawSlot) + && event.getClickedInventory().equals(inventory)) { + event.setCancelled(true); + return; + } + } + + if (rawSlot < 3 && rawSlot >= 0 || event.getAction().equals(InventoryAction.MOVE_TO_OTHER_INVENTORY)) { + event.setCancelled(true); + if (clickHandlerRunning && !concurrentClickHandlerExecution) { + // A click handler is running, don't launch another one + return; + } + + final CompletableFuture> actionsFuture = + clickHandler.apply(rawSlot, StateSnapshot.fromAnvilGUI(AnvilGUI.this)); + + final Consumer> actionsConsumer = actions -> { + for (final ResponseAction action : actions) { + action.accept(AnvilGUI.this, clicker); + } + }; + + if (actionsFuture.isDone()) { + // Fast-path without scheduling if clickHandler is performed in sync + // Because the future is already completed, .join() will not block the server thread + actionsFuture.thenAccept(actionsConsumer).join(); + } else { + clickHandlerRunning = true; + // If the plugin is disabled and the Executor throws an exception, the exception will be passed to + // the .handle method + actionsFuture + .thenAcceptAsync(actionsConsumer, mainThreadExecutor) + .handle((results, exception) -> { + if (exception != null) { + plugin.getLogger() + .log( + Level.SEVERE, + "An exception occurred in the AnvilGUI clickHandler", + exception); + } + // Whether an exception occurred or not, set running to false + clickHandlerRunning = false; + return null; + }); + } + } + } + + @EventHandler + public void onInventoryDrag(InventoryDragEvent event) { + if (event.getInventory().equals(inventory)) { + event.setCancelled(true); + } + } + + @EventHandler + public void onInventoryClose(InventoryCloseEvent event) { + if (open && event.getInventory().equals(inventory)) { + closeInventory(false); + if (preventClose) { + mainThreadExecutor.execute(AnvilGUI.this::openInventory); + } + } + } + } + + /** A builder class for an {@link AnvilGUI} object */ + public static class Builder { + + /** An {@link Executor} that executes tasks on the main server thread */ + private Executor mainThreadExecutor; + /** An {@link Consumer} that is called when the anvil GUI is close */ + private Consumer closeListener; + /** A flag that decides whether the async click handler can be run concurrently */ + private boolean concurrentClickHandlerExecution = false; + /** An {@link Function} that is called when a slot in the inventory has been clicked */ + private ClickHandler clickHandler; + /** A state that decides where the anvil GUI is able to be closed by the user */ + private boolean preventClose = false; + /** A set of integers containing the slot numbers that should be modifiable by the user. */ + private Set interactableSlots = Collections.emptySet(); + /** The {@link Plugin} that this anvil GUI is associated with */ + private Plugin plugin; + /** The text that will be displayed to the user */ + private Object titleComponent = WRAPPER.literalChatComponent("Repair & Name"); + /** The starting text on the item */ + private String itemText; + /** An {@link ItemStack} to be put in the left input slot */ + private ItemStack itemLeft; + /** An {@link ItemStack} to be put in the right input slot */ + private ItemStack itemRight; + /** An {@link ItemStack} to be placed in the output slot */ + private ItemStack itemOutput; + + /** + * Set a custom main server thread executor. Useful for plugins targeting Folia. + * + * @param executor The executor to run tasks on + * @return The {@link Builder} instance + * @throws IllegalArgumentException when the executor is null + */ + public Builder mainThreadExecutor(@NonNull Executor executor) { + this.mainThreadExecutor = executor; + return this; + } + + /** + * Prevents the closing of the anvil GUI by the user + * + * @return The {@link Builder} instance + */ + public Builder preventClose() { + preventClose = true; + return this; + } + + /** + * Permit the user to modify (take items in and out) the slot numbers provided. + * + * @param slots A varags param for the slot numbers. You can avoid relying on magic constants by using + * the {@link AnvilGUI.Slot} class. + * @return The {@link Builder} instance + */ + public Builder interactableSlots(int... slots) { + final Set newValue = new HashSet<>(); + for (int slot : slots) { + newValue.add(slot); + } + interactableSlots = newValue; + return this; + } + + /** + * Listens for when the inventory is closed + * + * @param closeListener An {@link Consumer} that is called when the anvil GUI is closed + * @return The {@link Builder} instance + * @throws IllegalArgumentException when the closeListener is null + */ + public Builder onClose(@NonNull Consumer closeListener) { + this.closeListener = closeListener; + return this; + } + + /** + * Do an action when a slot is clicked in the inventory + *

+ * The ClickHandler is only called when the previous execution of the ClickHandler has finished. + * To alter this behaviour use {@link #allowConcurrentClickHandlerExecution()} + * + * @param clickHandler A {@link ClickHandler} that is called when the user clicks a slot. The + * {@link Integer} is the slot number corresponding to {@link Slot}, the + * {@link StateSnapshot} contains information about the current state of the anvil, + * and the response is a {@link CompletableFuture} that will eventually return a + * list of {@link ResponseAction} to execute in the order that they are supplied. + * @return The {@link Builder} instance + * @throws IllegalArgumentException when the function supplied is null + */ + public Builder onClickAsync(@NonNull ClickHandler clickHandler) { + this.clickHandler = clickHandler; + return this; + } + + /** + * By default, the {@link #onClickAsync(ClickHandler) async click handler} will not run concurrently + * and instead wait for the previous {@link CompletableFuture} to finish before executing it again. + *

+ * If this trait is desired, it can be enabled by calling this method but may lead to inconsistent + * behaviour if not handled properly. + * + * @return The {@link Builder} instance + */ + public Builder allowConcurrentClickHandlerExecution() { + this.concurrentClickHandlerExecution = true; + return this; + } + + /** + * Do an action when a slot is clicked in the inventory + * + * @param clickHandler A {@link BiFunction} that is called when the user clicks a slot. The + * {@link Integer} is the slot number corresponding to {@link Slot}, the + * {@link StateSnapshot} contains information about the current state of the anvil, + * and the response is a list of {@link ResponseAction} to execute in the order + * that they are supplied. + * @return The {@link Builder} instance + * @throws IllegalArgumentException when the function supplied is null + */ + public Builder onClick(@NonNull BiFunction> clickHandler) { + this.clickHandler = + (slot, stateSnapshot) -> CompletableFuture.completedFuture(clickHandler.apply(slot, stateSnapshot)); + return this; + } + + /** + * Sets the plugin for the {@link AnvilGUI} + * + * @param plugin The {@link Plugin} this anvil GUI is associated with + * @return The {@link Builder} instance + * @throws IllegalArgumentException if the plugin is null + */ + public Builder plugin(@NonNull Plugin plugin) { + this.plugin = plugin; + return this; + } + + /** + * Sets the initial item-text that is displayed to the user. + *

+ * If the usage of Adventure Components is desired, you must create an item, set the displayname of it + * and put it into the AnvilGUI via {@link #itemLeft(ItemStack)} manually. + * + * @param text The initial name of the item in the anvil + * @return The {@link Builder} instance + * @throws IllegalArgumentException if the text is null + */ + public Builder text(@NonNull String text) { + this.itemText = text; + return this; + } + + /** + * Sets the AnvilGUI title that is to be displayed to the user. + *
+ * The provided title will be treated as literal text. + * + * @param title The title that is to be displayed to the user + * @return The {@link Builder} instance + * @throws IllegalArgumentException if the title is null + */ + public Builder title(@NonNull String title) { + this.titleComponent = WRAPPER.literalChatComponent(title); + return this; + } + + /** + * Sets the AnvilGUI title that is to be displayed to the user. + *
+ * The provided json will be parsed into rich chat components. + * + * @param json The title that is to be displayed to the user + * @return The {@link Builder} instance + * @throws IllegalArgumentException if the title is null + * @see net.md_5.bungee.chat.ComponentSerializer#toString(BaseComponent) + */ + public Builder jsonTitle(@NonNull String json) { + this.titleComponent = WRAPPER.jsonChatComponent(json); + return this; + } + + /** + * Sets the {@link ItemStack} to be put in the first slot + * + * @param item The {@link ItemStack} to be put in the first slot + * @return The {@link Builder} instance + * @throws IllegalArgumentException if the {@link ItemStack} is null + */ + public Builder itemLeft(@NonNull ItemStack item) { + this.itemLeft = item; + return this; + } + + /** + * Sets the {@link ItemStack} to be put in the second slot + * + * @param item The {@link ItemStack} to be put in the second slot + * @return The {@link Builder} instance + */ + public Builder itemRight(ItemStack item) { + this.itemRight = item; + return this; + } + + /** + * Sets the {@link ItemStack} to be put in the output slot + * + * @param item The {@link ItemStack} to be put in the output slot + * @return The {@link Builder} instance + */ + public Builder itemOutput(ItemStack item) { + this.itemOutput = item; + return this; + } + + /** + * Creates the anvil GUI and opens it for the player + * + * @param player The {@link Player} the anvil GUI should open for + * @return The {@link AnvilGUI} instance from this builder + * @throws IllegalArgumentException when the onClick function, plugin, or player is null + */ + public AnvilGUI open(@NonNull Player player) { + if (plugin == null) { + throw new IllegalStateException("An AnvilGUI plugin has not been set!"); + } + if (clickHandler == null) { + throw new IllegalStateException("An AnvilGUI clickHandler has not been set!"); + } + + if (itemText != null) { + if (itemLeft == null) { + itemLeft = new ItemStack(Material.PAPER); + } + + ItemMeta paperMeta = itemLeft.getItemMeta(); + paperMeta.setDisplayName(itemText); + itemLeft.setItemMeta(paperMeta); + } + + // If no executor is specified, execute all tasks with the BukkitScheduler + if (mainThreadExecutor == null) { + mainThreadExecutor = task -> Bukkit.getScheduler().runTask(plugin, task); + } + + final AnvilGUI anvilGUI = new AnvilGUI( + plugin, + player, + mainThreadExecutor, + titleComponent, + new ItemStack[] {itemLeft, itemRight, itemOutput}, + preventClose, + interactableSlots, + closeListener, + concurrentClickHandlerExecution, + clickHandler); + anvilGUI.openInventory(); + return anvilGUI; + } + } + + /** + * A handler that is called when the user clicks a slot. The + * {@link Integer} is the slot number corresponding to {@link Slot}, the + * {@link StateSnapshot} contains information about the current state of the anvil, + * and the response is a {@link CompletableFuture} that will eventually return a + * list of {@link ResponseAction} to execute in the order that they are supplied. + */ + @FunctionalInterface + public interface ClickHandler extends BiFunction>> {} + + /** An action to run in response to a player clicking the output slot in the GUI. This interface is public + * and permits you, the developer, to add additional response features easily to your custom AnvilGUIs. */ + @FunctionalInterface + public interface ResponseAction extends BiConsumer { + + /** + * Replace the input text box value with the provided text value. + * + * Before using this method, it must be verified by the caller that items are either in + * {@link Slot#INPUT_LEFT} or {@link Slot#OUTPUT} present. + * + * @param text The text to write in the input box + * @return The {@link ResponseAction} to achieve the text replacement + * @throws IllegalArgumentException when the text is null + * @throws IllegalStateException when the slots {@link Slot#INPUT_LEFT} and {@link Slot#OUTPUT} are null + */ + static ResponseAction replaceInputText(@NonNull String text) { + return (anvilgui, player) -> { + ItemStack item = anvilgui.getInventory().getItem(Slot.OUTPUT); + if (item == null) { + // Fallback on left input slot if player hasn't typed anything yet + item = anvilgui.getInventory().getItem(Slot.INPUT_LEFT); + } + if (item == null) { + throw new IllegalStateException( + "replaceInputText can only be used if slots OUTPUT or INPUT_LEFT are not empty"); + } + + final ItemStack cloned = item.clone(); + final ItemMeta meta = cloned.getItemMeta(); + meta.setDisplayName(text); + cloned.setItemMeta(meta); + anvilgui.getInventory().setItem(Slot.INPUT_LEFT, cloned); + }; + } + + /** + * Updates the title of the AnvilGUI to the new one. + * + * @param literalTitle The title to use as literal text + * @param preserveRenameText Whether to preserve the entered rename text + * @throws IllegalArgumentException when literalTitle is null + * @see Builder#title(String) + */ + static ResponseAction updateTitle(@NonNull String literalTitle, boolean preserveRenameText) { + return (anvilGUI, player) -> anvilGUI.setTitle(literalTitle, preserveRenameText); + } + + /** + * Updates the title of the AnvilGUI to the new one. + * + * @param json The json used to parse into a rich chat component + * @param preserveRenameText Whether to preserve the entered rename text + * @throws IllegalArgumentException when json is null + * @see Builder#jsonTitle(String) + */ + static ResponseAction updateJsonTitle(@NonNull String json, boolean preserveRenameText) { + return (anvilGUI, player) -> anvilGUI.setJsonTitle(json, preserveRenameText); + } + + /** + * Open another inventory + * @param otherInventory The inventory to open + * @return The {@link ResponseAction} to achieve the inventory open + * @throws IllegalArgumentException when the otherInventory is null + */ + static ResponseAction openInventory(@NonNull Inventory otherInventory) { + return (anvilgui, player) -> player.openInventory(otherInventory); + } + + /** + * Close the AnvilGUI + * @return The {@link ResponseAction} to achieve closing the AnvilGUI + */ + static ResponseAction close() { + return (anvilgui, player) -> anvilgui.closeInventory(); + } + + /** + * Run the provided runnable + * @param runnable The runnable to run + * @return The {@link ResponseAction} to achieve running the runnable + * @throws IllegalArgumentException when the runnable is null + */ + static ResponseAction run(@NonNull Runnable runnable) { + return (anvilgui, player) -> runnable.run(); + } + } + + /** + * Represents a response when the player clicks the output item in the anvil GUI + * @deprecated Since 1.6.2, use {@link ResponseAction} + */ + @Deprecated + public static class Response { + /** + * Returns an {@link Response} object for when the anvil GUI is to close + * @return An {@link Response} object for when the anvil GUI is to display text to the user + * @deprecated Since 1.6.2, use {@link ResponseAction#close()} + */ + public static List close() { + return Arrays.asList(ResponseAction.close()); + } + + /** + * Returns an {@link Response} object for when the anvil GUI is to display text to the user + * + * @param text The text that is to be displayed to the user + * @return A list containing the {@link ResponseAction} for legacy compat + * @deprecated Since 1.6.2, use {@link ResponseAction#replaceInputText(String)} + */ + public static List text(String text) { + return Arrays.asList(ResponseAction.replaceInputText(text)); + } + + /** + * Returns an {@link Response} object for when the GUI should open the provided inventory + * + * @param inventory The inventory to open + * @return A list containing the {@link ResponseAction} for legacy compat + * @deprecated Since 1.6.2, use {@link ResponseAction#openInventory(Inventory)} + */ + public static List openInventory(Inventory inventory) { + return Arrays.asList(ResponseAction.openInventory(inventory)); + } + } + + /** + * Class wrapping the magic constants of slot numbers in an anvil GUI + */ + public static class Slot { + + private static final int[] values = new int[] {Slot.INPUT_LEFT, Slot.INPUT_RIGHT, Slot.OUTPUT}; + + /** + * The slot on the far left, where the first input is inserted. An {@link ItemStack} is always inserted + * here to be renamed + */ + public static final int INPUT_LEFT = 0; + /** + * Not used, but in a real anvil you are able to put the second item you want to combine here + */ + public static final int INPUT_RIGHT = 1; + /** + * The output slot, where an item is put when two items are combined from {@link #INPUT_LEFT} and + * {@link #INPUT_RIGHT} or {@link #INPUT_LEFT} is renamed + */ + public static final int OUTPUT = 2; + + /** + * Get all anvil slot values + * + * @return The array containing all possible anvil slots + */ + public static int[] values() { + return values; + } + } + + /** Represents a snapshot of the state of an AnvilGUI */ + public static final class StateSnapshot { + + /** + * Create an {@link StateSnapshot} from the current state of an {@link AnvilGUI} + * @param anvilGUI The instance to take the snapshot of + * @return The snapshot + */ + private static StateSnapshot fromAnvilGUI(AnvilGUI anvilGUI) { + final Inventory inventory = anvilGUI.getInventory(); + return new StateSnapshot( + itemNotNull(inventory.getItem(Slot.INPUT_LEFT)).clone(), + itemNotNull(inventory.getItem(Slot.INPUT_RIGHT)).clone(), + itemNotNull(inventory.getItem(Slot.OUTPUT)).clone(), + anvilGUI.player); + } + + /** + * The {@link ItemStack} in the anvilGui slots + */ + private final ItemStack leftItem, rightItem, outputItem; + + /** + * The {@link Player} that clicked the output slot + */ + private final Player player; + + /** + * The event parameter constructor + * @param leftItem The left item in the combine slot of the anvilGUI + * @param rightItem The right item in the combine slot of the anvilGUI + * @param outputItem The item that would have been outputted, when the items would have been combined + * @param player The player that clicked the output slot + */ + public StateSnapshot(ItemStack leftItem, ItemStack rightItem, ItemStack outputItem, Player player) { + this.leftItem = leftItem; + this.rightItem = rightItem; + this.outputItem = outputItem; + this.player = player; + } + + /** + * It returns the item in the left combine slot of the gui + * + * @return The leftItem + */ + public ItemStack getLeftItem() { + return leftItem; + } + + /** + * It returns the item in the right combine slot of the gui + * + * @return The rightItem + */ + public ItemStack getRightItem() { + return rightItem; + } + + /** + * It returns the output item that would have been the result + * by combining the left and right one + * + * @return The outputItem + */ + public ItemStack getOutputItem() { + return outputItem; + } + + /** + * It returns the player that clicked onto the output slot + * + * @return The player + */ + public Player getPlayer() { + return player; + } + + /** + * It returns the text the player typed into the rename field + * + * @return The text of the rename field + */ + public String getText() { + return outputItem.hasItemMeta() ? outputItem.getItemMeta().getDisplayName() : ""; + } + } +} \ No newline at end of file diff --git a/SpigotCore/SpigotCore_Main/src/net/wesjd/anvilgui/version/VersionWrapper.java b/SpigotCore/SpigotCore_Main/src/net/wesjd/anvilgui/version/VersionWrapper.java new file mode 100644 index 00000000..24d07d2a --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/net/wesjd/anvilgui/version/VersionWrapper.java @@ -0,0 +1,151 @@ +package net.wesjd.anvilgui.version; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.component.DataComponents; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.game.ClientboundContainerClosePacket; +import net.minecraft.network.protocol.game.ClientboundOpenScreenPacket; +import net.minecraft.network.protocol.game.ClientboundSetExperiencePacket; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.inventory.AnvilMenu; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.inventory.Slot; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.event.CraftEventFactory; +import org.bukkit.craftbukkit.util.CraftChatMessage; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.inventory.Inventory; + +public final class VersionWrapper { + public static final VersionWrapper INSTANCE = new VersionWrapper(); + + /** + * Interface implemented by the custom NMS AnvilContainer used to interact with it directly + */ + public interface AnvilContainerWrapper { + + /** + * Retrieves the raw text that has been entered into the Anvil at the moment + *

+ * This field is marked as public in the Minecraft AnvilContainer only from Minecraft 1.11 and upwards + * + * @return The raw text in the rename field + */ + default String getRenameText() { + return null; + } + + /** + * Sets the provided text as the literal hovername of the item in the left input slot + * + * @param text The text to set + */ + default void setRenameText(String text) { + } + + /** + * Gets the {@link Inventory} wrapper of the NMS container + * + * @return The inventory of the NMS container + */ + Inventory getBukkitInventory(); + } + + private static class AnvilContainer extends AnvilMenu implements AnvilContainerWrapper { + public AnvilContainer(Player player, int containerId, Component guiTitle) { + super( + containerId, + ((CraftPlayer) player).getHandle().getInventory(), + ContainerLevelAccess.create(((CraftWorld) player.getWorld()).getHandle(), new BlockPos(0, 0, 0)) + ); + this.checkReachable = false; + setTitle(guiTitle); + } + + public int getContainerId() { + return this.containerId; + } + + public String getRenameText() { + return this.itemName; + } + + public void setRenameText(String text) { + // If an item is present in the left input slot change its hover name to the literal text. + Slot inputLeft = getSlot(0); + if (inputLeft.hasItem()) { + inputLeft.getItem() + .set(DataComponents.CUSTOM_NAME, Component.literal(text)); + } + } + + public Inventory getBukkitInventory() { + return this.getBukkitView().getTopInventory(); + } + } + + private int getRealNextContainerId(Player player) { + return toNMS(player).nextContainerCounter(); + } + + /** + * Turns a {@link Player} into an NMS one + * + * @param player The player to be converted + * @return the NMS EntityPlayer + */ + private ServerPlayer toNMS(Player player) { + return ((CraftPlayer) player).getHandle(); + } + + public int getNextContainerId(Player player, AnvilContainerWrapper container) { + return ((AnvilContainer) container).getContainerId(); + } + + public void handleInventoryCloseEvent(Player player) { + CraftEventFactory.handleInventoryCloseEvent(toNMS(player), InventoryCloseEvent.Reason.UNKNOWN); + toNMS(player).doCloseContainer(); + } + + public void sendPacketOpenWindow(Player player, int containerId, Object inventoryTitle) { + toNMS(player).connection.send(new ClientboundOpenScreenPacket(containerId, MenuType.ANVIL, (Component) inventoryTitle)); + } + + public void sendPacketCloseWindow(Player player, int containerId) { + toNMS(player).connection.send(new ClientboundContainerClosePacket(containerId)); + } + + public void sendPacketExperienceChange(Player player, int experienceLevel) { + toNMS(player).connection.send(new ClientboundSetExperiencePacket(0f, 0, experienceLevel)); + } + + public void setActiveContainerDefault(Player player) { + toNMS(player).containerMenu = toNMS(player).inventoryMenu; + } + + public void setActiveContainer(Player player, AnvilContainerWrapper container) { + toNMS(player).containerMenu = (AnvilMenu) container; + } + + public void setActiveContainerId(AnvilContainerWrapper container, int containerId) { + } + + public void addActiveContainerSlotListener(AnvilContainerWrapper container, Player player) { + toNMS(player).initMenu((AnvilMenu) container); + } + + public AnvilContainerWrapper newContainerAnvil(Player player, Object title) { + return new AnvilContainer(player, getRealNextContainerId(player), (Component) title); + } + + public Object literalChatComponent(String content) { + return Component.literal(content); + } + + public Object jsonChatComponent(String json) { + return CraftChatMessage.fromJSON(json); + } +} \ No newline at end of file diff --git a/SpigotCore/SpigotCore_Main/src/spigotcore.accesswidener b/SpigotCore/SpigotCore_Main/src/spigotcore.accesswidener new file mode 100644 index 00000000..68d0aa82 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/spigotcore.accesswidener @@ -0,0 +1,71 @@ +accessWidener v2 named + +# For TinyProtocol and CheckpointUtilsJ9 +accessible field net/minecraft/server/network/ServerConnectionListener channels Ljava/util/List; + +# For ErrorHandler +accessible field org/spigotmc/WatchdogThread instance Lorg/spigotmc/WatchdogThread; + +# For ResourceKey +accessible method net/minecraft/resources/ResourceKey (Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/resources/ResourceLocation;)V +accessible method net/minecraft/resources/ResourceLocation (Ljava/lang/String;Ljava/lang/String;)V + +# For SteamwarGameProfileRepository +accessible field net/minecraft/server/MinecraftServer services Lnet/minecraft/server/Services; +mutable field net/minecraft/server/MinecraftServer services Lnet/minecraft/server/Services; + +# REntity +## transitive-extendable means that a public no args constructor is added without any super initialization +transitive-extendable class net/minecraft/network/protocol/game/ClientboundAnimatePacket +accessible field net/minecraft/network/protocol/game/ClientboundAnimatePacket id I +mutable field net/minecraft/network/protocol/game/ClientboundAnimatePacket id I +accessible field net/minecraft/network/protocol/game/ClientboundAnimatePacket action I +mutable field net/minecraft/network/protocol/game/ClientboundAnimatePacket action I + +## transitive-extendable means that a public no args constructor is added without any super initialization +transitive-extendable class net/minecraft/network/protocol/game/ClientboundEntityEventPacket +accessible field net/minecraft/network/protocol/game/ClientboundEntityEventPacket entityId I +mutable field net/minecraft/network/protocol/game/ClientboundEntityEventPacket entityId I +accessible field net/minecraft/network/protocol/game/ClientboundEntityEventPacket eventId B +mutable field net/minecraft/network/protocol/game/ClientboundEntityEventPacket eventId B + +## transitive-extendable means that a public no args constructor is added without any super initialization +transitive-extendable class net/minecraft/network/protocol/game/ClientboundRotateHeadPacket +accessible field net/minecraft/network/protocol/game/ClientboundRotateHeadPacket yHeadRot B +mutable field net/minecraft/network/protocol/game/ClientboundRotateHeadPacket yHeadRot B +## + TechHider +accessible field net/minecraft/network/protocol/game/ClientboundRotateHeadPacket entityId I +mutable field net/minecraft/network/protocol/game/ClientboundRotateHeadPacket entityId I + +# For ChunkHider +## transitive-extendable means that a public no args constructor is added without any super initialization +transitive-extendable class net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket +accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket chunkData Lnet/minecraft/network/protocol/game/ClientboundLevelChunkPacketData; +mutable field net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket chunkData Lnet/minecraft/network/protocol/game/ClientboundLevelChunkPacketData; + +## transitive-extendable means that a public no args constructor is added without any super initialization +transitive-extendable class net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData +accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData buffer [B +mutable field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData buffer [B +accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData blockEntitiesData Ljava/util/List; +mutable field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData blockEntitiesData Ljava/util/List; + +accessible class net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData$BlockEntityInfo +accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData$BlockEntityInfo type Lnet/minecraft/world/level/block/entity/BlockEntityType; +accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData$BlockEntityInfo packedXZ I +accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData$BlockEntityInfo y I + +# For TechHider +accessible field net/minecraft/network/protocol/game/ClientboundMoveEntityPacket entityId I +## transitive-extendable means that a public no args constructor is added without any super initialization +transitive-extendable class net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket +accessible field net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket sectionPos Lnet/minecraft/core/SectionPos; +accessible field net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket positions [S +mutable field net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket positions [S +accessible field net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket states [Lnet/minecraft/world/level/block/state/BlockState; +mutable field net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket states [Lnet/minecraft/world/level/block/state/BlockState; + +# For legacy/TechHider +## transitive-extendable means that a public no args constructor is added without any super initialization +transitive-extendable class net/minecraft/network/protocol/game/ClientboundBlockUpdatePacket +mutable field net/minecraft/network/protocol/game/ClientboundBlockUpdatePacket blockState Lnet/minecraft/world/level/block/state/BlockState; \ No newline at end of file diff --git a/VelocityCore/src/de/steamwar/velocitycore/Node.java b/VelocityCore/src/de/steamwar/velocitycore/Node.java index c7f68c70..82eec3d1 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/Node.java +++ b/VelocityCore/src/de/steamwar/velocitycore/Node.java @@ -63,7 +63,7 @@ public abstract class Node { nodes.forEach(consumer); } - public abstract ProcessBuilder startServer(String serverJar, File directory, String worldDir, String levelName, int port, String... dParams); + public abstract ProcessBuilder startServer(ServerVersion version, File directory, String worldDir, String levelName, int port, String... dParams); protected abstract ProcessBuilder prepareExecution(String... command); @@ -97,7 +97,8 @@ public abstract class Node { return hostname; } - protected void constructServerstart(File directory, List cmd, String serverJar, String worldDir, String levelName, int port, String... dParams) { + protected void constructServerstart(File directory, List cmd, ServerVersion version, String worldDir, String levelName, int port, String... dParams) { + String serverJar = version.getServerJar(); if (JAVA_8.contains(serverJar)) { cmd.add("/usr/lib/jvm/openj9-8/bin/java"); } else { @@ -107,6 +108,10 @@ public abstract class Node { for (String param : dParams) { cmd.add("-D" + param); } + if (version.isExtendedStartup()) { + cmd.add("-Dpaper.disablePluginRemapping=true"); + cmd.add("-javaagent:/jars/AccessWidener.jar=start"); + } cmd.add("-Xshareclasses:nonfatal,name=" + directory.getName()); cmd.add("-Xmx768M"); cmd.addAll(OPENJ9_ARGS); @@ -145,9 +150,9 @@ public abstract class Node { } @Override - public ProcessBuilder startServer(String serverJar, File directory, String worldDir, String levelName, int port, String... dParams) { + public ProcessBuilder startServer(ServerVersion version, File directory, String worldDir, String levelName, int port, String... dParams) { List cmd = new ArrayList<>(); - constructServerstart(directory, cmd, serverJar, worldDir, levelName, port, dParams); + constructServerstart(directory, cmd, version, worldDir, levelName, port, dParams); ProcessBuilder builder = new ProcessBuilder(cmd); builder.directory(directory); return builder; @@ -177,7 +182,7 @@ public abstract class Node { } @Override - public ProcessBuilder startServer(String serverJar, File directory, String worldDir, String levelName, int port, String... dParams) { + public ProcessBuilder startServer(ServerVersion version, File directory, String worldDir, String levelName, int port, String... dParams) { List cmd = new ArrayList<>(); cmd.add("ssh"); cmd.add("-L"); @@ -186,7 +191,7 @@ public abstract class Node { cmd.add("cd"); cmd.add(directory.getPath()); cmd.add(";"); - constructServerstart(directory, cmd, serverJar, worldDir, levelName, port, dParams); + constructServerstart(directory, cmd, version, worldDir, levelName, port, dParams); return new ProcessBuilder(cmd); } diff --git a/VelocityCore/src/de/steamwar/velocitycore/ServerStarter.java b/VelocityCore/src/de/steamwar/velocitycore/ServerStarter.java index 55bf4d36..85a537f8 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/ServerStarter.java +++ b/VelocityCore/src/de/steamwar/velocitycore/ServerStarter.java @@ -325,7 +325,7 @@ public class ServerStarter { private void regularStart(String serverName, int port) { postStart(constructor.construct(serverName, port, node.startServer( - version.getServerJar(), directory, worldDir, worldName, port, arguments.entrySet().stream().map(entry -> entry.getKey() + "=" + entry.getValue()).toArray(String[]::new) + version, directory, worldDir, worldName, port, arguments.entrySet().stream().map(entry -> entry.getKey() + "=" + entry.getValue()).toArray(String[]::new) ), worldCleanup, null)); } diff --git a/VelocityCore/src/de/steamwar/velocitycore/ServerVersion.java b/VelocityCore/src/de/steamwar/velocitycore/ServerVersion.java index 59f52f0f..f7b2dff9 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/ServerVersion.java +++ b/VelocityCore/src/de/steamwar/velocitycore/ServerVersion.java @@ -23,6 +23,7 @@ import com.velocitypowered.api.network.ProtocolVersion; import de.steamwar.sql.GameModeConfig; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.RequiredArgsConstructor; import java.io.File; import java.util.HashMap; @@ -33,6 +34,7 @@ import java.util.regex.Pattern; @Getter @AllArgsConstructor +@RequiredArgsConstructor public enum ServerVersion { SPIGOT_8("spigot-1.8.8.jar", 8, ProtocolVersion.MINECRAFT_1_8), SPIGOT_9("spigot-1.9.4.jar", 9, ProtocolVersion.MINECRAFT_1_9), @@ -47,7 +49,7 @@ public enum ServerVersion { PAPER_18("paper-1.18.2.jar", 15, ProtocolVersion.MINECRAFT_1_18_2), PAPER_19("paper-1.19.3.jar", 19, ProtocolVersion.MINECRAFT_1_19_3), PAPER_20("paper-1.20.1.jar", 20, ProtocolVersion.MINECRAFT_1_20), - PAPER_21("paper-1.21.6.jar", 21, ProtocolVersion.MINECRAFT_1_21_6); + PAPER_21("paper-1.21.6.jar", 21, ProtocolVersion.MINECRAFT_1_21_6, true); private static final Map chatMap = new HashMap<>(); @@ -104,6 +106,10 @@ public enum ServerVersion { private final String serverJar; private final int versionSuffix; private final ProtocolVersion protocolVersion; + /** + * Adding AccessWidener agent and setting System Property (paper.disablePluginRemapping) to true + */ + private boolean extendedStartup; public String getWorldFolder(String base) { return base + versionSuffix + "/"; diff --git a/VelocityCore/src/de/steamwar/velocitycore/commands/ReplayCommand.java b/VelocityCore/src/de/steamwar/velocitycore/commands/ReplayCommand.java index b6bdd7ec..ae276519 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/commands/ReplayCommand.java +++ b/VelocityCore/src/de/steamwar/velocitycore/commands/ReplayCommand.java @@ -42,13 +42,6 @@ public class ReplayCommand extends SWCommand { super("replay"); } - @Register - public void genericCommand(PlayerChatter sender) { - sender.system("REPLAY_UNAVAILABLE"); - return; - } - - /* @Register public void genericCommand(PlayerChatter sender, int replayId, @StaticValue(value = {"", "-a"}, allowISE = true) @OptionalValue("") boolean isAdmin, @OptionalValue("") String optionalMap) { Fight fight = Fight.getById(replayId); @@ -64,6 +57,9 @@ public class ReplayCommand extends SWCommand { @Register public void genericCommand(PlayerChatter sender, @OptionalValue("") String optionalMap) { if (PunishmentCommand.isPunishedWithMessage(sender, Punishment.PunishmentType.NoFightServer)) return; + if (!sender.user().hasPerm(UserPerm.TEAM)) return; + + new SWStreamInv<>(sender, new Message("REPLAY_TITLE"), (click, fight) -> { startReplay(sender, click.isShiftClick(), optionalMap, fight); @@ -72,6 +68,7 @@ public class ReplayCommand extends SWCommand { private void startReplay(PlayerChatter sender, boolean isAdmin, String optionalMap, Fight fight) { if (PunishmentCommand.isPunishedWithMessage(sender, Punishment.PunishmentType.NoFightServer)) return; + if (!sender.user().hasPerm(UserPerm.TEAM)) return; GameModeConfig mode = ArenaMode.getBySchemType(fight.getSchemType()); ServerStarter starter = new ServerStarter().replay(fight.getFightID()).blueLeader(sender.getPlayer()); @@ -116,5 +113,5 @@ public class ReplayCommand extends SWCommand { private Message parseLeader(SteamwarUser leader, int players, boolean winner) { return new Message("REPLAY_" + (players > 1 ? "" : "SOLO_") + (winner ? "WINNER" : "LOSER"), leader.getUserName(), players - 1); } - */ + } diff --git a/VelocityCore/src/de/steamwar/velocitycore/discord/DiscordBot.java b/VelocityCore/src/de/steamwar/velocitycore/discord/DiscordBot.java index 9cbd4061..7e7dba1e 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/discord/DiscordBot.java +++ b/VelocityCore/src/de/steamwar/velocitycore/discord/DiscordBot.java @@ -22,7 +22,6 @@ package de.steamwar.velocitycore.discord; import de.steamwar.command.SWCommand; import de.steamwar.messages.Chatter; import de.steamwar.sql.Event; -import de.steamwar.sql.SteamwarUser; import de.steamwar.velocitycore.VelocityCore; import de.steamwar.velocitycore.discord.channels.*; import de.steamwar.velocitycore.discord.listeners.ChannelListener; @@ -53,12 +52,12 @@ import net.dv8tion.jda.api.utils.MemberCachePolicy; import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder; import java.awt.*; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; -import java.util.stream.Collectors; public class DiscordBot { public static final String ARGUMENT_NAME = "arguments"; diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle.kts similarity index 82% rename from buildSrc/build.gradle rename to buildSrc/build.gradle.kts index e1ee5bb0..3df3ec61 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle.kts @@ -1,7 +1,7 @@ /* * This file is a part of the SteamWar software. * - * Copyright (C) 2025 SteamWar.de-Serverteam + * 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 @@ -18,21 +18,22 @@ */ plugins { - id 'groovy-gradle-plugin' -} - -sourceSets { - main { - groovy { - srcDirs("src/") - } - } + `kotlin-dsl` + `groovy-gradle-plugin` } repositories { mavenCentral() + gradlePluginPortal() +} + +sourceSets { + main { + groovy.srcDirs("src/main/groovy") + kotlin.srcDirs("src/main/kotlin") + } } dependencies { implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.21") -} \ No newline at end of file +} diff --git a/buildSrc/src/steamwar.devserver.gradle b/buildSrc/src/main/groovy/steamwar.devserver.gradle similarity index 92% rename from buildSrc/src/steamwar.devserver.gradle rename to buildSrc/src/main/groovy/steamwar.devserver.gradle index 20b82def..71b0027d 100644 --- a/buildSrc/src/steamwar.devserver.gradle +++ b/buildSrc/src/main/groovy/steamwar.devserver.gradle @@ -1,7 +1,7 @@ /* * This file is a part of the SteamWar software. * - * Copyright (C) 2025 SteamWar.de-Serverteam + * 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 @@ -27,8 +27,7 @@ plugins { class DevServer extends DefaultTask { @Input - @Optional - String worldName = null + boolean debug = false @Input String template = null @@ -65,6 +64,10 @@ class DevServer extends DefaultTask { @Optional Boolean forceUpgrade = null + @Input + @Optional + String worldName = null + DevServer() { super() doFirst { @@ -84,7 +87,13 @@ class DevServer extends DefaultTask { } } - if (worldName == null) worldName = properties.get("worldName") + if (template.startsWith("Bau")) { + if (properties.containsKey("worldName")) { + worldName = properties.get("worldName") + } else { + throw new GradleException("Please supply the 'worldName' in a 'steamwar.properties' files either in this project dir or any parent project!") + } + } host = properties.get("host") debugPort = new Random().nextInt(5001, 10000) @@ -95,7 +104,7 @@ class DevServer extends DefaultTask { doLast { setupTemplate(template) uploadDependencies() - startDebugPort() + if (debug) startDebugPort() startDevServer() } finalizedBy(new Finalizer()) @@ -240,8 +249,10 @@ class DevServer extends DefaultTask { for (Map.Entry dParam : dParams.entrySet()) { devPy.append(" -D${dParam.key}=${dParam.value}") } + devPy.append(" -Dpaper.disablePluginRemapping=true") devPy.append(" $template") - devPy.append(" -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:$debugPort") + if (debug) devPy.append(" -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:$debugPort") + devPy.append(" -javaagent:/jars/AccessWidener.jar=start") if (jvmArgs != null) devPy.append(" $jvmArgs") println("Starting $template with command ${devPy.toString()}") @@ -286,7 +297,7 @@ class VelocityServer extends DevServer { VelocityServer() { super() doFirst { - if(packetDecodeLogging) dParams.put("velocity.packet-decode-logging", "true") + if (packetDecodeLogging) dParams.put("velocity.packet-decode-logging", "true") } } } diff --git a/buildSrc/src/steamwar.java.gradle b/buildSrc/src/main/groovy/steamwar.java.gradle similarity index 100% rename from buildSrc/src/steamwar.java.gradle rename to buildSrc/src/main/groovy/steamwar.java.gradle diff --git a/buildSrc/src/steamwar.kotlin.gradle b/buildSrc/src/main/groovy/steamwar.kotlin.gradle similarity index 100% rename from buildSrc/src/steamwar.kotlin.gradle rename to buildSrc/src/main/groovy/steamwar.kotlin.gradle diff --git a/buildSrc/src/main/kotlin/WidenerExtension.kt b/buildSrc/src/main/kotlin/WidenerExtension.kt new file mode 100644 index 00000000..73ae4e5b --- /dev/null +++ b/buildSrc/src/main/kotlin/WidenerExtension.kt @@ -0,0 +1,50 @@ +/* + * 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 . + */ + +import org.gradle.api.Project +import org.gradle.api.artifacts.MinimalExternalModuleDependency +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.provider.Provider +import javax.inject.Inject + +abstract class WidenerExtension { + + @get:Inject + abstract val project: Project + + abstract val inputJars: ConfigurableFileCollection + abstract val accessWidenerFiles: ConfigurableFileCollection + + fun fromCatalog(vararg dependencies: Provider) { + dependencies.map { dependency -> + project.provider { + val dep = dependency.get() + + project.configurations.getByName("compileClasspath") + .resolvedConfiguration + .resolvedArtifacts + .first { + it.moduleVersion.id.module.group == dep.module.group && + it.moduleVersion.id.module.name == dep.module.name + } + .file + } + }.let { inputJars.from(it) } + } +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/widener.gradle.kts b/buildSrc/src/main/kotlin/widener.gradle.kts new file mode 100644 index 00000000..c85ae3d5 --- /dev/null +++ b/buildSrc/src/main/kotlin/widener.gradle.kts @@ -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 . + */ + +val widener = extensions.create("widener") + +widener.accessWidenerFiles.setFrom( + fileTree("src/") { include("**/*.accesswidener") } +) + +val jarWidenerClasspath = rootProject.project(":AccessWidener") + .tasks.named("shadowJar") + .get().outputs.files + +val allWidenedJars = objects.fileCollection() + +project.gradle.projectsEvaluated { + if (widener.inputJars.isEmpty) { + logger.warn("[widener] No input JARs configured for ${project.name} — widenedJar tasks will not be registered.") + return@projectsEvaluated + } + + widener.inputJars.forEachIndexed { index, inputJar -> + val taskName = if (index == 0) "widenedJar" else "widenedJar$index" + val outputFile = layout.buildDirectory + .file("widened/${inputJar.nameWithoutExtension}-widened.jar") + + val task = tasks.register(taskName) { + description = "Produces a widened copy of ${inputJar.name} for compile-time use." + group = "widener" + + inputs.file(inputJar) + inputs.files(widener.accessWidenerFiles) + outputs.file(outputFile) + + classpath = jarWidenerClasspath + mainClass.set("de.steamwar.Main") + + dependsOn(rootProject.project(":AccessWidener").tasks.named("shadowJar")) + + doFirst { + args = buildList { + add(inputJar.absolutePath) + add(outputFile.get().asFile.absolutePath) + addAll(widener.accessWidenerFiles.map { it.absolutePath }) + } + } + } + + allWidenedJars.from(task) + } + + tasks.named("compileJava") { + dependsOn(allWidenedJars) + } +} + +project.dependencies { + add("compileOnly", project.fileTree("build/widened")) +} diff --git a/settings.gradle.kts b/settings.gradle.kts index b84535d4..c3da5d8d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -162,6 +162,10 @@ dependencyResolutionManagement { } } +include( + "AccessWidener" +) + include( "BauSystem", "BauSystem:BauSystem_Main",