diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index e9f4d842..6aced9ef 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -51,6 +51,7 @@ jobs: rm -rf deploy mkdir -p deploy + cp "AccessWidener/build/libs/AccessWidener-all.jar" "deploy/AccessWidener.jar" cp "BauSystem/build/libs/BauSystem-all.jar" "deploy/BauSystem.jar" cp "FightSystem/build/libs/FightSystem-all.jar" "deploy/FightSystem.jar" cp "KotlinCore/build/libs/KotlinCore-all.jar" "deploy/KotlinCore.jar" diff --git a/.gitignore b/.gitignore index dd7af7f6..ba7d8990 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,11 @@ bin/ .vscode .settings +# Language Server +**/.project +**/.factorypath +**/.classpath + # Other lib /WebsiteBackend/data diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/FlatteningWrapper.java b/AccessWidener/build.gradle.kts similarity index 59% rename from SpigotCore/SpigotCore_Main/src/de/steamwar/core/FlatteningWrapper.java rename to AccessWidener/build.gradle.kts index 6d6d4d1b..a6632cf1 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/FlatteningWrapper.java +++ b/AccessWidener/build.gradle.kts @@ -17,18 +17,29 @@ * along with this program. If not, see . */ -package de.steamwar.core; +plugins { + `java-library` + alias(libs.plugins.shadow) +} -import lombok.experimental.UtilityClass; -import net.minecraft.network.chat.MutableComponent; -import net.minecraft.network.chat.contents.PlainTextContents; +dependencies { + implementation("org.ow2.asm:asm:9.7") + implementation("org.ow2.asm:asm-commons:9.7") +} -import java.util.Optional; - -@UtilityClass -public class FlatteningWrapper { - - public Object formatDisplayName(String displayName) { - return displayName != null ? Optional.of((Object) MutableComponent.create(PlainTextContents.create(displayName))) : Optional.empty(); +tasks.shadowJar { + manifest { + attributes( + "Manifest-Version" to "1.0", + "Build-Jdk-Spec" to "21", + "Main-Class" to "de.steamwar.Main", + "Premain-Class" to "de.steamwar.Agent", + "Can-Retransform-Classes" to "true", + "Can-Redefine-Classes" to "true", + ) } } + +tasks.build { + dependsOn(tasks.shadowJar) +} diff --git a/AccessWidener/src/main/java/de/steamwar/AccessWidenerEntry.java b/AccessWidener/src/main/java/de/steamwar/AccessWidenerEntry.java new file mode 100644 index 00000000..5b5ff9a0 --- /dev/null +++ b/AccessWidener/src/main/java/de/steamwar/AccessWidenerEntry.java @@ -0,0 +1,49 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar; + +/** + * A single parsed line from a .accesswidener file. + *

+ * Examples: + * accessible class net/minecraft/server/level/ServerPlayer + * accessible method net/minecraft/server/level/ServerPlayer getStats ()V + * mutable field net/minecraft/world/entity/Entity id I + * extendable class net/minecraft/world/level/chunk/LevelChunk + */ +public record AccessWidenerEntry( + /** accessible | mutable | extendable (may have "transitive-" prefix) */ + String directive, + /** class | method | field */ + String memberType, + /** Internal class name, e.g. net/minecraft/server/level/ServerPlayer */ + String target, + /** Method/field name, null for class entries */ + String name, + /** Descriptor, null for class entries */ + String descriptor) { + /** + * Returns true if this entry targets the class with the given internal name. + */ + public boolean targets(String internalName) { + return target.equals(internalName); + } +} + diff --git a/AccessWidener/src/main/java/de/steamwar/AccessWidenerParser.java b/AccessWidener/src/main/java/de/steamwar/AccessWidenerParser.java new file mode 100644 index 00000000..d962e0dc --- /dev/null +++ b/AccessWidener/src/main/java/de/steamwar/AccessWidenerParser.java @@ -0,0 +1,104 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +/** + * Parses Fabric-compatible .accesswidener files. + *

+ * Supported format: + *

+ *   accessWidener v2 named
+ *
+ *   # comments are supported
+ *   accessible class  net/minecraft/Foo
+ *   accessible method net/minecraft/Foo someMethod ()V
+ *   accessible field  net/minecraft/Foo someField I
+ *   mutable    field  net/minecraft/Foo someField I
+ *   extendable class  net/minecraft/Foo
+ *   extendable method net/minecraft/Foo someMethod ()V
+ *
+ *   # transitive variants (expose widening to dependents)
+ *   transitive-accessible class net/minecraft/Foo
+ * 
+ */ +public final class AccessWidenerParser { + + private AccessWidenerParser() { + } + + public static List parse(InputStream in) throws IOException { + List entries = new ArrayList<>(); + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) { + + String line; + boolean headerSeen = false; + + while ((line = reader.readLine()) != null) { + // Strip inline comments + int commentIdx = line.indexOf('#'); + if (commentIdx >= 0) line = line.substring(0, commentIdx); + line = line.strip(); + + if (line.isEmpty()) continue; + + if (!headerSeen) { + // First non-blank, non-comment line must be the header + if (!line.startsWith("accessWidener")) { + throw new IOException("Missing accessWidener header, got: " + line); + } + headerSeen = true; + continue; + } + + AccessWidenerEntry entry = parseLine(line); + if (entry != null) entries.add(entry); + } + } + + return entries; + } + + private static AccessWidenerEntry parseLine(String line) { + String[] parts = line.split("\\s+"); + if (parts.length < 3) return null; + + String directive = parts[0]; // accessible / mutable / extendable / transitive-* + String memberType = parts[1]; // class / method / field + String target = parts[2]; // internal class name + + return switch (memberType) { + case "class" -> new AccessWidenerEntry(directive, "class", target, null, null); + case "method", "field" -> { + if (parts.length < 5) yield null; + yield new AccessWidenerEntry(directive, memberType, target, parts[3], parts[4]); + } + default -> null; + }; + } +} \ No newline at end of file diff --git a/AccessWidener/src/main/java/de/steamwar/Agent.java b/AccessWidener/src/main/java/de/steamwar/Agent.java new file mode 100644 index 00000000..e078d357 --- /dev/null +++ b/AccessWidener/src/main/java/de/steamwar/Agent.java @@ -0,0 +1,75 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar; + +import java.io.File; +import java.io.IOException; +import java.lang.instrument.Instrumentation; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; + +/** + * Java agent entry point. + *

+ * 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 6d594c79..c6316f7d 100644 --- a/BauSystem/BauSystem_Main/build.gradle.kts +++ b/BauSystem/BauSystem_Main/build.gradle.kts @@ -19,6 +19,7 @@ plugins { steamwar.java + widener } tasks.compileJava { @@ -48,3 +49,7 @@ dependencies { implementation(libs.luaj) implementation(files("$projectDir/../libs/YAPION-SNAPSHOT.jar")) } + +widener { + fromCatalog(libs.nms) +} diff --git a/BauSystem/BauSystem_Main/src/bausystem.accesswidener b/BauSystem/BauSystem_Main/src/bausystem.accesswidener new file mode 100644 index 00000000..5dd89fdf --- /dev/null +++ b/BauSystem/BauSystem_Main/src/bausystem.accesswidener @@ -0,0 +1,13 @@ +accessWidener v2 named + +# For NoClipCommand +accessible field net/minecraft/server/level/ServerPlayerGameMode gameModeForPlayer Lnet/minecraft/world/level/GameType; + +# For PlaceItemUtils +accessible field org/bukkit/craftbukkit/block/CraftBlockState position Lnet/minecraft/core/BlockPos; +mutable field org/bukkit/craftbukkit/block/CraftBlockState position Lnet/minecraft/core/BlockPos; +accessible field org/bukkit/craftbukkit/block/CraftBlockState world Lorg/bukkit/craftbukkit/CraftWorld; +mutable field org/bukkit/craftbukkit/block/CraftBlockState world Lorg/bukkit/craftbukkit/CraftWorld; + +# For TickManager +accessible field net/minecraft/server/ServerTickRateManager remainingSprintTicks J diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/region/TestblockCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/region/TestblockCommand.java index 1644aaea..bce22006 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/region/TestblockCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/region/TestblockCommand.java @@ -185,17 +185,16 @@ public class TestblockCommand extends SWCommand { return new TypeMapper() { @Override public List tabCompletes(CommandSender commandSender, PreviousArguments previousArguments, String s) { - List stringList = new ArrayList<>(SchematicNode.getNodeTabcomplete(SteamwarUser.get(((Player) commandSender).getUniqueId()), s)); + List stringList = new ArrayList<>(); stringList.addAll(SchematicNode.getNodeTabcomplete(SteamwarUser.byId(0), s)); + stringList.addAll(SchematicNode.getNodeTabcomplete(SteamwarUser.get(((Player) commandSender).getUniqueId()), s)); return stringList; } @Override public SchematicNode map(CommandSender commandSender, PreviousArguments previousArguments, String s) { - SchematicNode node = SchematicNode.getNodeFromPath(SteamwarUser.get(((Player) commandSender).getUniqueId()), s); - if (node == null) { - node = SchematicNode.getNodeFromPath(SteamwarUser.byId(0), s); - } + SchematicNode node = SchematicNode.getNodeFromPath(SteamwarUser.byId(0), s); + if (node == null) node = SchematicNode.getNodeFromPath(SteamwarUser.get(((Player) commandSender).getUniqueId()), s); return node; } }; diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/SimulatorCursor.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/SimulatorCursor.java index 73bbfcd7..72178e23 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/SimulatorCursor.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/SimulatorCursor.java @@ -55,19 +55,11 @@ import org.bukkit.event.Listener; import org.bukkit.event.block.Action; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryDragEvent; -import org.bukkit.event.player.PlayerDropItemEvent; -import org.bukkit.event.player.PlayerItemHeldEvent; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.event.player.PlayerToggleSneakEvent; +import org.bukkit.event.player.*; import org.bukkit.inventory.ItemStack; import org.bukkit.util.Vector; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/blastresistance/BlastResistanceCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/blastresistance/BlastResistanceCommand.java index 7c9430a7..7afb0e5f 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/blastresistance/BlastResistanceCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/slaves/blastresistance/BlastResistanceCommand.java @@ -32,7 +32,6 @@ import de.steamwar.linkage.Linked; import org.bukkit.Material; import org.bukkit.entity.Player; -import java.util.*; import java.util.concurrent.atomic.AtomicInteger; @Linked diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/TechHiderCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/TechHiderCommand.java index 4744e41d..2f401a59 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/TechHiderCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/techhider/TechHiderCommand.java @@ -31,6 +31,7 @@ import de.steamwar.command.TypeMapper; import de.steamwar.core.CraftbukkitWrapper; import de.steamwar.linkage.Linked; import de.steamwar.linkage.LinkedInstance; +import de.steamwar.techhider.legacy.TechHider; import net.md_5.bungee.api.ChatMessageType; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; @@ -38,7 +39,6 @@ import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerQuitEvent; -import de.steamwar.techhider.legacy.TechHider; import java.util.*; import java.util.stream.Collectors; diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/Trace.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/Trace.java index 95107761..8eb49d79 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/Trace.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/Trace.java @@ -25,7 +25,6 @@ import de.steamwar.bausystem.features.tracer.rendering.TraceEntity; import de.steamwar.bausystem.features.tracer.rendering.ViewFlag; import de.steamwar.bausystem.region.Region; import de.steamwar.entity.REntity; -import de.steamwar.entity.REntityAction; import de.steamwar.entity.REntityServer; import lombok.Getter; import lombok.Setter; diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/rendering/TraceEntity.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/rendering/TraceEntity.java index e2be5eb9..160e4d58 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/rendering/TraceEntity.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/rendering/TraceEntity.java @@ -48,9 +48,9 @@ import static de.steamwar.bausystem.features.util.TNTClickListener.TNT_CLICK_DET */ public class TraceEntity extends RBlockDisplay { - private static final float TNT_VISUAL_SCALE = 0.98F; - private static final float TNT_VISUAL_OFFSET = -TNT_VISUAL_SCALE / 2.0F; - private static final Transformation TNT_VISUAL_TRANSFORM = new Transformation( + public static final float TNT_VISUAL_SCALE = 0.98F; + public static final float TNT_VISUAL_OFFSET = -TNT_VISUAL_SCALE / 2.0F; + public static final Transformation TNT_VISUAL_TRANSFORM = new Transformation( new Vector3f(TNT_VISUAL_OFFSET, 0.0F, TNT_VISUAL_OFFSET), new Quaternionf(0, 0, 0, 1), new Vector3f(TNT_VISUAL_SCALE, TNT_VISUAL_SCALE, TNT_VISUAL_SCALE), diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/rendering/ViewFlag.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/rendering/ViewFlag.java index 3938d88e..da08fecd 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/rendering/ViewFlag.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/tracer/rendering/ViewFlag.java @@ -131,6 +131,7 @@ public abstract class ViewFlag { if (yLocation.distanceSquared(representative.getLocation()) >= 1.0 / 256.0 && yLocation.distanceSquared(previous.getLocation()) >= 1.0 / 256.0) { RBlockDisplay y = new RBlockDisplay(server, yLocation); y.setBlock(Material.WHITE_STAINED_GLASS.createBlockData()); + y.setTransform(TraceEntity.TNT_VISUAL_TRANSFORM); } Location secoundLocation; @@ -143,6 +144,7 @@ public abstract class ViewFlag { if (secoundLocation.distanceSquared(representative.getLocation()) >= 1.0 / 256.0 && secoundLocation.distanceSquared(previous.getLocation()) >= 1.0 / 256.0) { RBlockDisplay second = new RBlockDisplay(server, secoundLocation); second.setBlock(Material.WHITE_STAINED_GLASS.createBlockData()); + second.setTransform(TraceEntity.TNT_VISUAL_TRANSFORM); } } } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/BindCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/BindCommand.java index bde7967e..5db75a81 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/BindCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/BindCommand.java @@ -38,6 +38,7 @@ import org.bukkit.Bukkit; import org.bukkit.NamespacedKey; import org.bukkit.command.CommandMap; import org.bukkit.command.CommandSender; +import org.bukkit.craftbukkit.CraftServer; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -47,7 +48,6 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.persistence.PersistentDataType; -import java.lang.reflect.Field; import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -73,19 +73,7 @@ public class BindCommand extends SWCommand implements Listener { } } - private static final CommandMap commandMap; - - static { - Field knownCommandsField; - try { - knownCommandsField = Bukkit.getServer().getClass().getDeclaredField("commandMap"); - knownCommandsField.setAccessible(true); - commandMap = (CommandMap) knownCommandsField.get(Bukkit.getServer()); - } catch (IllegalAccessException | NoSuchFieldException var2) { - Bukkit.shutdown(); - throw new SecurityException("Oh shit. Commands cannot be registered.", var2); - } - } + private static final CommandMap commandMap = ((CraftServer) Bukkit.getServer()).getCommandMap(); private static final NamespacedKey KEY = SWUtils.getNamespaceKey("command"); diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/NoClipCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/NoClipCommand.java index 0b10e163..dd143f0a 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/NoClipCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/util/NoClipCommand.java @@ -21,7 +21,6 @@ package de.steamwar.bausystem.features.util; import com.comphenix.tinyprotocol.TinyProtocol; import com.mojang.authlib.GameProfile; -import de.steamwar.Reflection; import de.steamwar.bausystem.BauSystem; import de.steamwar.bausystem.features.tpslimit.TPSUtils; import de.steamwar.bausystem.utils.BauMemberUpdateEvent; @@ -30,7 +29,6 @@ import de.steamwar.core.ProtocolWrapper; import de.steamwar.core.SWPlayer; import de.steamwar.linkage.Linked; import net.minecraft.network.protocol.game.*; -import net.minecraft.server.level.ServerPlayerGameMode; import net.minecraft.world.entity.player.Abilities; import net.minecraft.world.level.GameType; import org.bukkit.Bukkit; @@ -103,10 +101,8 @@ public class NoClipCommand extends SWCommand implements Listener { TinyProtocol.instance.addFilter(ServerboundSetCreativeModeSlotPacket.class, third); } - private static final Reflection.Field playerGameMode = Reflection.getField(ServerPlayerGameMode.class, GameType.class, 0); - private void setInternalGameMode(Player player, GameMode gameMode) { - playerGameMode.set(((CraftPlayer) player).getHandle().gameMode, GameType.byId(gameMode.getValue())); + ((CraftPlayer) player).getHandle().gameMode.gameModeForPlayer = GameType.byId(gameMode.getValue()); } @Register(help = true) @@ -143,6 +139,7 @@ public class NoClipCommand extends SWCommand implements Listener { @EventHandler(ignoreCancelled = true) public void onBlock(BlockCanBuildEvent event) { + if (event.getPlayer() == null) return; if (SWPlayer.of(event.getPlayer()).hasComponent(NoClipData.class)) { event.setBuildable(true); } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/BauMemberUpdate.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/BauMemberUpdate.java index ed5b068b..d3416e08 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/BauMemberUpdate.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/BauMemberUpdate.java @@ -36,8 +36,6 @@ import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.entity.PlayerDeathEvent; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.player.PlayerItemConsumeEvent; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/SpectatorListener.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/SpectatorListener.java index 92a5c59e..645600cd 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/SpectatorListener.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/SpectatorListener.java @@ -25,6 +25,7 @@ import de.steamwar.bausystem.config.BauServer; import de.steamwar.bausystem.utils.BauMemberUpdateEvent; import de.steamwar.linkage.Linked; import de.steamwar.sql.BauweltMember; +import de.steamwar.techhider.legacy.TechHider; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; @@ -39,7 +40,6 @@ import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.event.entity.EntityPickupItemEvent; import org.bukkit.event.player.*; import org.bukkit.util.Vector; -import de.steamwar.techhider.legacy.TechHider; import java.util.HashSet; import java.util.Set; diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/WorldEditListener.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/WorldEditListener.java index 26943331..b0373595 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/WorldEditListener.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/WorldEditListener.java @@ -57,7 +57,7 @@ public class WorldEditListener implements Listener { private static final Set commands = new HashSet<>(); private static final Set commandExclusions = new HashSet<>(); - private static final String[] shortcutCommands = {"//1", "//2", "//90", "//-90", "//180", "//p", "//c", "//flopy", "//floppy", "//flopyp", "//floppyp", "//u", "//r"}; + private static final String[] shortcutCommands = {"//1", "//2", "//90", "//-90", "//180", "//p", "//c", "//flopy", "//floppy", "//flopyp", "//floppyp", "//u", "//r", "//download", "/download"}; public static boolean isWorldEditCommand(String command) { for (String shortcut : shortcutCommands) { diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/xray/XrayCommand.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/xray/XrayCommand.java index 55bcbeeb..aa90081c 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/xray/XrayCommand.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/xray/XrayCommand.java @@ -28,6 +28,7 @@ import de.steamwar.command.SWCommand; import de.steamwar.core.CraftbukkitWrapper; import de.steamwar.linkage.Linked; import de.steamwar.linkage.LinkedInstance; +import de.steamwar.techhider.legacy.TechHider; import net.md_5.bungee.api.ChatMessageType; import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket; import net.minecraft.server.level.ServerPlayer; @@ -40,7 +41,6 @@ import org.bukkit.event.Listener; import org.bukkit.event.block.Action; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerQuitEvent; -import de.steamwar.techhider.legacy.TechHider; import java.util.*; import java.util.function.BiFunction; diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/PlaceItemUtils.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/PlaceItemUtils.java index c4ab9a54..71f42cbb 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/PlaceItemUtils.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/PlaceItemUtils.java @@ -19,7 +19,6 @@ package de.steamwar.bausystem.utils; -import de.steamwar.Reflection; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.experimental.UtilityClass; @@ -86,9 +85,6 @@ public class PlaceItemUtils { .collect(Collectors.toSet()); } - private static final Reflection.Field positionAccessor = Reflection.getField(CraftBlockState.class, BlockPos.class, 0); - private static final Reflection.Field worldAccessor = Reflection.getField(CraftBlockState.class, CraftWorld.class, 0); - /** * Attempt to place an {@link ItemStack} the {@link Player} is holding against a {@link Block} inside the World. * This can be easily used inside the {@link org.bukkit.event.player.PlayerInteractEvent} to mimik placing a @@ -288,8 +284,9 @@ public class PlaceItemUtils { } else { // If a BlockState is present set the Position and World to the Block you want to place Location blockLocation = block.getLocation(); - positionAccessor.set(blockState, new BlockPos(blockLocation.getBlockX(), blockLocation.getBlockY(), blockLocation.getBlockZ())); - worldAccessor.set(blockState, blockLocation.getWorld()); + CraftBlockState craftBlockState = (CraftBlockState) blockState; + craftBlockState.position = new BlockPos(blockLocation.getBlockX(), blockLocation.getBlockY(), blockLocation.getBlockZ()); + craftBlockState.world = (CraftWorld) blockLocation.getWorld(); } if (blockData.getMaterial().isSolid()) { diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/TickManager.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/TickManager.java index 59d12df2..613b3ea4 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/TickManager.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/TickManager.java @@ -20,7 +20,6 @@ package de.steamwar.bausystem.utils; import com.comphenix.tinyprotocol.TinyProtocol; -import de.steamwar.Reflection; import de.steamwar.bausystem.BauSystem; import net.minecraft.network.protocol.game.ClientboundTickingStatePacket; import net.minecraft.server.MinecraftServer; @@ -33,7 +32,6 @@ public class TickManager implements Listener { public static final TickManager impl = new TickManager(); private static final ServerTickRateManager manager = MinecraftServer.getServer().tickRateManager(); - private static final Reflection.Field remainingSprintTicks = Reflection.getField(ServerTickRateManager.class, long.class, 0); private boolean blockTpsPacket = true; private int totalSteps; @@ -121,7 +119,7 @@ public class TickManager implements Listener { public long getRemainingTicks() { if (isSprinting()) { - return remainingSprintTicks.get(manager); + return manager.remainingSprintTicks; } else { return manager.frozenTicksToRun(); } diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/WorldEditUtils.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/WorldEditUtils.java index 0bab1548..8c493707 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/WorldEditUtils.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/utils/WorldEditUtils.java @@ -19,6 +19,7 @@ package de.steamwar.bausystem.utils; +import com.fastasyncworldedit.core.regions.selector.PolyhedralRegionSelector; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.IncompleteRegionException; import com.sk89q.worldedit.LocalSession; @@ -32,16 +33,19 @@ import com.sk89q.worldedit.internal.registry.InputParser; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.regions.RegionSelector; +import com.sk89q.worldedit.regions.selector.*; import com.sk89q.worldedit.regions.selector.limit.SelectorLimits; -import de.steamwar.Reflection; +import com.sk89q.worldedit.world.World; import de.steamwar.bausystem.shared.Pair; import lombok.SneakyThrows; import lombok.experimental.UtilityClass; import org.bukkit.Location; -import org.bukkit.World; import org.bukkit.entity.Player; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.function.Function; import java.util.stream.Collectors; @UtilityClass @@ -91,17 +95,36 @@ public class WorldEditUtils { .getRegionSelector(BukkitAdapter.adapt(player.getWorld())); return new Pair<>(regionSelector.getClass(), regionSelector.getVertices() .stream() - .map(blockVector3 -> blockVector3 == null ? null : adapt(player.getWorld(), blockVector3)) + .map(blockVector3 -> { + if (blockVector3 == null) { + return null; + } else { + return BukkitAdapter.adapt(player.getWorld(), blockVector3); + } + }) .collect(Collectors.toList())); } + private static final Map, Function> constructors = new HashMap<>(); + static { + constructors.put(CuboidRegionSelector.class, CuboidRegionSelector::new); + constructors.put(ExtendingCuboidRegionSelector.class, ExtendingCuboidRegionSelector::new); + constructors.put(Polygonal2DRegionSelector.class, Polygonal2DRegionSelector::new); + constructors.put(EllipsoidRegionSelector.class, EllipsoidRegionSelector::new); + constructors.put(SphereRegionSelector.class, SphereRegionSelector::new); + constructors.put(CylinderRegionSelector.class, CylinderRegionSelector::new); + constructors.put(ConvexPolyhedralRegionSelector.class, ConvexPolyhedralRegionSelector::new); + constructors.put(PolyhedralRegionSelector.class, PolyhedralRegionSelector::new); + } + public void setVertices(Player player, Class 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/CommonCore/SQL/src/de/steamwar/sql/CheckedSchematic.kt b/CommonCore/SQL/src/de/steamwar/sql/CheckedSchematic.kt index a7b0104c..a8da2737 100644 --- a/CommonCore/SQL/src/de/steamwar/sql/CheckedSchematic.kt +++ b/CommonCore/SQL/src/de/steamwar/sql/CheckedSchematic.kt @@ -86,6 +86,24 @@ class CheckedSchematic(id: EntityID) : CompositeEntity(id) { useDb { find { (CheckedSchematicTable.nodeOwner eq owner.id) and (CheckedSchematicTable.seen eq false) }.orderBy(CheckedSchematicTable.endTime to SortOrder.DESC).toList() } + + @JvmStatic + fun countAccepted(owner: SteamwarUser) = + useDb { + find { (CheckedSchematicTable.nodeOwner eq owner.id) and (CheckedSchematicTable.declineReason eq "freigegeben") }.count() + } + + @JvmStatic + fun countAccepted(owner: SteamwarUser, type: String) = + useDb { + find { (CheckedSchematicTable.nodeOwner eq owner.id) and (CheckedSchematicTable.declineReason eq "freigegeben") and (CheckedSchematicTable.nodeType like "$type%") }.count() + } + + @JvmStatic + fun countChecked(validator: SteamwarUser) = + useDb { + find { CheckedSchematicTable.validator eq validator.id }.count() + } } val node by CheckedSchematicTable.nodeId.transform({ it?.let { EntityID(it, SchematicNodeTable) } }, { it?.value }) diff --git a/CommonCore/SQL/src/de/steamwar/sql/EventFight.kt b/CommonCore/SQL/src/de/steamwar/sql/EventFight.kt index ae214915..bdbce609 100644 --- a/CommonCore/SQL/src/de/steamwar/sql/EventFight.kt +++ b/CommonCore/SQL/src/de/steamwar/sql/EventFight.kt @@ -130,6 +130,43 @@ class EventFight(id: EntityID) : IntEntity(id), Comparable { } ) } + + @JvmStatic + fun countEventFights(fighter: SteamwarUser) = + useDb { + exec( + "SELECT COUNT(DISTINCT F.FightID) AS FightCount FROM FightPlayer INNER JOIN Fight F on FightPlayer.FightID = F.FightID INNER JOIN EventFight EF on F.FightID = EF.Fight WHERE UserID = ?", + args = listOf(IntegerColumnType() to fighter.id.value) + ) { + if (it.next()) { + it.getLong("FightCount") + } else { + 0 + } + } + ?: 0 + } + + @JvmStatic + fun countPlacement(fighter: SteamwarUser, placement: Int) = + useDb { + exec( + """ + SELECT COUNT(DISTINCT EventFight.EventID) AS PlacementCount FROM TeamTeilnahme + INNER JOIN EventFight ON EventFight.EventID = TeamTeilnahme.EventID + INNER JOIN FightPlayer ON FightPlayer.FightID = EventFight.Fight + WHERE (IF(FightPlayer.Team = 1, EventFight.TeamBlue, EventFight.TeamRed)) = TeamTeilnahme.TeamID AND UserID = ? AND Placement = ? + """.trimIndent(), + args = listOf(IntegerColumnType() to fighter.id.value, IntegerColumnType() to placement) + ) { + if (it.next()) { + it.getInt("PlacementCount") + } else { + 0 + } + } + ?: 0 + } } val fightID by EventFightTable.id.transform({ EntityID(it, EventFightTable) }, { it.value }) diff --git a/CommonCore/SQL/src/de/steamwar/sql/FightPlayer.kt b/CommonCore/SQL/src/de/steamwar/sql/FightPlayer.kt index 626440fb..23ff78a0 100644 --- a/CommonCore/SQL/src/de/steamwar/sql/FightPlayer.kt +++ b/CommonCore/SQL/src/de/steamwar/sql/FightPlayer.kt @@ -20,9 +20,12 @@ package de.steamwar.sql import de.steamwar.sql.internal.useDb +import org.jetbrains.exposed.v1.core.IntegerColumnType +import org.jetbrains.exposed.v1.core.VarCharColumnType import org.jetbrains.exposed.v1.core.dao.id.CompositeID import org.jetbrains.exposed.v1.core.dao.id.CompositeIdTable import org.jetbrains.exposed.v1.core.dao.id.EntityID +import org.jetbrains.exposed.v1.core.eq import org.jetbrains.exposed.v1.core.inList import org.jetbrains.exposed.v1.dao.CompositeEntity import org.jetbrains.exposed.v1.dao.CompositeEntityClass @@ -69,6 +72,28 @@ class FightPlayer(id: EntityID) : CompositeEntity(id) { useDb { find { FightPlayerTable.fightId inList fightIds.toList() }.toList() } + + @JvmStatic + fun countFights(userId: Int) = + useDb { + find { FightPlayerTable.userId eq userId }.count() + } + + @JvmStatic + fun countFights(userId: Int, type: String) = + useDb { + exec( + "SELECT COUNT(*) AS FightCount FROM FightPlayer INNER JOIN Fight F on FightPlayer.FightID = F.FightID WHERE UserID = ? AND GameMode LIKE ?", + args = listOf(IntegerColumnType() to userId, VarCharColumnType() to "$type%") + ) { + if (it.next()) { + it.getInt("FightCount") + } else { + 0 + } + } + ?: 0 + } } val fightID by FightPlayerTable.fightId.transform({ EntityID(it, FightTable) }, { it.value }) diff --git a/CommonCore/SQL/src/de/steamwar/sql/GameModeConfig.java b/CommonCore/SQL/src/de/steamwar/sql/GameModeConfig.java index f8b27f59..d22d8978 100644 --- a/CommonCore/SQL/src/de/steamwar/sql/GameModeConfig.java +++ b/CommonCore/SQL/src/de/steamwar/sql/GameModeConfig.java @@ -146,12 +146,12 @@ public final class GameModeConfig { public final List CheckQuestions; /** - * The allowed checkers to check this schematic type denoted by a list of SteamWar ids. + * The allowed checkers to check this schematic type denoted by a list of SteamwarUser ids. * The people need the {@link UserPerm#CHECK} to be able to check though. * * @implSpec {@code []} by default -> denoting every person with {@link UserPerm#CHECK} can check it */ - public final List Checkers; + public final Set Checkers; /** * Bundle for countdowns during the fight @@ -246,7 +246,7 @@ public final class GameModeConfig { } CheckQuestions = loader.getStringList("CheckQuestions"); - Checkers = loader.getIntList("Checkers"); + Checkers = loader.getIntSet("Checkers"); Times = new TimesConfig(loader.with("Times")); // Arena would be here to be in config order but needs Schematic.Size and EnterStages loaded afterwards Schematic = new SchematicConfig<>(loader.with("Schematic")); diff --git a/CommonCore/SQL/src/de/steamwar/sql/YMLWrapper.java b/CommonCore/SQL/src/de/steamwar/sql/YMLWrapper.java index 39eff9bb..567524ff 100644 --- a/CommonCore/SQL/src/de/steamwar/sql/YMLWrapper.java +++ b/CommonCore/SQL/src/de/steamwar/sql/YMLWrapper.java @@ -139,6 +139,12 @@ final class YMLWrapper { return get(path, o -> (List) o); } + public Set getIntSet(String path) { + List list = get(path, o -> (List) o); + if (list.isEmpty()) return Collections.emptySet(); + return Collections.unmodifiableSet(new HashSet<>(list)); + } + public List getSchematicTypeList(String path) { List list = getStringList(path); if (list.isEmpty()) { 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/Permanent.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Permanent.java index 75340bd5..e5c55436 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Permanent.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Permanent.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 @@ -31,17 +31,16 @@ import de.steamwar.linkage.Linked; import net.md_5.bungee.api.ChatMessageType; import org.bukkit.GameMode; import org.bukkit.Material; +import org.bukkit.Tag; import org.bukkit.block.Block; import org.bukkit.block.data.type.Dispenser; +import org.bukkit.block.data.type.DriedGhast; import org.bukkit.entity.Player; import org.bukkit.entity.TNTPrimed; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.event.block.BlockDispenseEvent; -import org.bukkit.event.block.BlockFromToEvent; -import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.block.*; import org.bukkit.event.entity.EntityExplodeEvent; import org.bukkit.event.entity.FoodLevelChangeEvent; import org.bukkit.event.entity.PlayerDeathEvent; @@ -258,4 +257,16 @@ public class Permanent implements Listener { event.setCancelled(true); FightSystem.getMessage().sendPrefixless("NO_BLOCK_BREAK", event.getPlayer(), ChatMessageType.ACTION_BAR); } + + /** + * Prevents Dried Ghast from spawning and plants from growing + */ + @EventHandler + public void onBlockGrow(BlockGrowEvent event) { + var type = event.getBlock().getType(); + + if (event.getBlock().getBlockData() instanceof DriedGhast || Tag.CROPS.isTagged(type) || Tag.SAPLINGS.isTagged(type)) { + event.setCancelled(true); + } + } } 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..aaa0ca63 100644 --- a/LobbySystem/src/de/steamwar/lobby/map/CustomMap.java +++ b/LobbySystem/src/de/steamwar/lobby/map/CustomMap.java @@ -42,7 +42,10 @@ import java.awt.image.BufferedImage; import java.awt.image.WritableRaster; import java.io.File; import java.io.IOException; +import java.time.LocalDateTime; +import java.time.Month; import java.util.*; +import java.util.concurrent.atomic.AtomicReference; public class CustomMap implements Listener { @@ -56,7 +59,7 @@ public class CustomMap implements Listener { new Vector(2346, 45, 1297), new Vector(2345, 45, 1297), new Vector(2344, 45, 1297), new Vector(2343, 45, 1297), new Vector(2342, 45, 1297), new Vector(2341, 45, 1297), new Vector(2340, 45, 1297) ); - private static final CustomMap RIGHT = new CustomMap(new File(System.getProperty("user.home") + "/lobbyBanner/right.png"), + private static final CustomMap RIGHT = new CustomMap(new File(System.getProperty("user.home") + "/lobbyBanner/right/"), new Vector(2330, 48, 1297), new Vector(2329, 48, 1297), new Vector(2328, 48, 1297), new Vector(2327, 48, 1297), new Vector(2326, 48, 1297), new Vector(2325, 48, 1297), new Vector(2324, 48, 1297), new Vector(2330, 47, 1297), new Vector(2329, 47, 1297), new Vector(2328, 47, 1297), new Vector(2327, 47, 1297), new Vector(2326, 47, 1297), new Vector(2325, 47, 1297), new Vector(2324, 47, 1297), new Vector(2330, 46, 1297), new Vector(2329, 46, 1297), new Vector(2328, 46, 1297), new Vector(2327, 46, 1297), new Vector(2326, 46, 1297), new Vector(2325, 46, 1297), new Vector(2324, 46, 1297), @@ -66,32 +69,51 @@ public class CustomMap implements Listener { private File mapFile; private Map itemFrameIndex = new HashMap<>(); private ItemFrame[] itemFrames; - private long lastModified = Long.MAX_VALUE; + private boolean update = true; - public CustomMap(File mapFile, Vector... itemFrames) { - this.mapFile = mapFile; + public CustomMap(File mapFileOrDirectory, Vector... itemFrames) { + this.mapFile = mapFileOrDirectory; this.itemFrames = new ItemFrame[itemFrames.length]; for (int i = 0; i < itemFrames.length; i++) { itemFrameIndex.put(itemFrames[i], i); } - Bukkit.getScheduler().runTaskTimer(LobbySystem.getInstance(), () -> { - long modified = mapFile.lastModified(); - if (modified > lastModified) { - lastModified = modified; - System.out.println("Updating Banner: " + mapFile.getName()); - Bukkit.getScheduler().runTaskAsynchronously(LobbySystem.getInstance(), () -> { - try { - run(); - } catch (IOException e) { - // Ignore - } - }); - } - }, 200L, 200L); + if (mapFileOrDirectory.isDirectory()) { + AtomicReference lastMonth = new AtomicReference<>(LocalDateTime.now().getMonth()); + Bukkit.getScheduler().runTaskTimer(LobbySystem.getInstance(), () -> { + Month current = LocalDateTime.now().getMonth(); + if (!current.equals(lastMonth.get()) || update) { + lastMonth.set(current); + update = false; + this.mapFile = new File(mapFileOrDirectory, current.getValue() + ".png"); + update(); + } + }, 200L, 1200L); + } else { + AtomicReference lastModified = new AtomicReference<>(Long.MAX_VALUE); + Bukkit.getScheduler().runTaskTimer(LobbySystem.getInstance(), () -> { + long modified = mapFileOrDirectory.lastModified(); + if (modified > lastModified.get() || update) { + lastModified.set(modified); + update = false; + update(); + } + }, 200L, 200L); + } Bukkit.getPluginManager().registerEvents(this, LobbySystem.getInstance()); } + private void update() { + System.out.println("Updating Banner: " + mapFile.getName()); + Bukkit.getScheduler().runTaskAsynchronously(LobbySystem.getInstance(), () -> { + try { + run(); + } catch (IOException e) { + // Ignore + } + }); + } + @EventHandler public void onChunkLoad(ChunkLoadEvent event) { for (Entity entity : event.getChunk().getEntities()) { @@ -101,7 +123,7 @@ public class CustomMap implements Listener { if (itemFrameIndex.containsKey(vector)) { if (itemFrames[itemFrameIndex.get(vector)] != null) continue; itemFrames[itemFrameIndex.get(vector)] = itemFrame; - lastModified = 0; + update = true; ItemStack itemStack = new ItemStack(Material.FILLED_MAP, 1); MapMeta mapMeta = (MapMeta) itemStack.getItemMeta(); @@ -254,7 +276,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/build.gradle.kts b/MissileWars/build.gradle.kts index b85213d3..f347a5ec 100644 --- a/MissileWars/build.gradle.kts +++ b/MissileWars/build.gradle.kts @@ -29,5 +29,15 @@ dependencies { compileOnly(libs.paperapi) compileOnly(libs.nms) - compileOnly(libs.worldedit) + compileOnly(libs.fawe) +} + +tasks.register("MissileWars21") { + group = "run" + description = "Run a 1.21 Dev MissileWars" + dependsOn(":SpigotCore:shadowJar") + dependsOn(":MissileWars:jar") + template = "MissileWars" + worldName = "Great_Wall" + jar = "/jars/paper-1.21.6.jar" } diff --git a/MissileWars/src/de/steamwar/misslewars/items/Missile.java b/MissileWars/src/de/steamwar/misslewars/items/Missile.java index e9ba0593..78160342 100644 --- a/MissileWars/src/de/steamwar/misslewars/items/Missile.java +++ b/MissileWars/src/de/steamwar/misslewars/items/Missile.java @@ -30,6 +30,8 @@ import com.sk89q.worldedit.function.operation.Operations; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.transform.AffineTransform; import com.sk89q.worldedit.session.ClipboardHolder; +import com.sk89q.worldedit.util.SideEffect; +import com.sk89q.worldedit.util.SideEffectSet; import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.block.BlockTypes; import de.steamwar.misslewars.MissileWars; @@ -109,11 +111,17 @@ public class Missile extends SpecialItem { v = aT.apply(v.toVector3()).toBlockPoint(); v = v.add(location.getBlockX(), location.getBlockY(), location.getBlockZ()); - EditSession e = WorldEdit.getInstance().getEditSessionFactory().getEditSession(world, -1); + EditSession e = WorldEdit.getInstance().getEditSessionFactory() + .getEditSession(world, -1); + e.setSideEffectApplier(SideEffectSet.defaults() + .with(SideEffect.NEIGHBORS, SideEffect.State.ON) + .with(SideEffect.LIGHTING, SideEffect.State.ON) + .with(SideEffect.UPDATE, SideEffect.State.ON)); ClipboardHolder ch = new ClipboardHolder(clipboard); ch.setTransform(aT); Operations.completeBlindly(ch.createPaste(e).to(v).ignoreAirBlocks(true).build()); e.flushSession(); + return true; } 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..1d7c0d68 100644 --- a/SchematicSystem/src/de/steamwar/schematicsystem/autocheck/AutoChecker.java +++ b/SchematicSystem/src/de/steamwar/schematicsystem/autocheck/AutoChecker.java @@ -37,28 +37,38 @@ 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)) - .entities(clipboard.getEntities().stream().map(Entity::getLocation) - .map(blockVector3 -> new BlockPos(blockVector3.getBlockX(), blockVector3.getBlockY(), blockVector3.getBlockZ())) - .collect(Collectors.toList())) + return AutoCheckerResult.builder() + .type(type) + .height(clipboard.getDimensions().y()) + .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())) .build(); } 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 +79,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/Dependencies/src/de/steamwar/velocitycore/advancements/Items.java b/VelocityCore/Dependencies/src/de/steamwar/velocitycore/advancements/Items.java new file mode 100644 index 00000000..617c2902 --- /dev/null +++ b/VelocityCore/Dependencies/src/de/steamwar/velocitycore/advancements/Items.java @@ -0,0 +1,43 @@ +/* + * 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.velocitycore.advancements; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.URI; + +public class Items { + + /** + * Loaded from https://github.com/retrooper/packetevents/blob/2.0/mappings/registries/item.json + */ + public static final JsonObject values; + + static { + try { + values = new Gson().fromJson(new BufferedReader(new InputStreamReader(URI.create("https://raw.githubusercontent.com/retrooper/packetevents/refs/heads/2.0/mappings/registries/item.json").toURL().openConnection().getInputStream())), JsonObject.class); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } +} diff --git a/VelocityCore/Persistent/src/de/steamwar/persistent/Subserver.java b/VelocityCore/Persistent/src/de/steamwar/persistent/Subserver.java index 26e60b21..88902b9a 100644 --- a/VelocityCore/Persistent/src/de/steamwar/persistent/Subserver.java +++ b/VelocityCore/Persistent/src/de/steamwar/persistent/Subserver.java @@ -80,7 +80,7 @@ public class Subserver { static void shutdown() { while (!serverList.isEmpty()) { Subserver server = serverList.get(0); - server.stop(); + server.sleep(); } } @@ -138,7 +138,7 @@ public class Subserver { writer.println(command); } - public void stop() { + public void sleep() { try { long pid = process.pid(); if (checkpoint) { @@ -152,9 +152,32 @@ public class Subserver { try { if (!process.waitFor(1, TimeUnit.MINUTES)) { - logger.log(Level.SEVERE, () -> serverName + " did not stop correctly, forcibly stopping!"); - process.destroyForcibly(); + forceStop(); } + } catch (InterruptedException e) { + logger.log(Level.SEVERE, "Subserver stop interrupted!", e); + Thread.currentThread().interrupt(); + } + } + + public void stop() { + try { + process.destroy(); + if (!process.waitFor(1, TimeUnit.MINUTES)) { + forceStop(); + } else if (thread.isAlive()) { + thread.join(); + } + } catch (InterruptedException e) { + logger.log(Level.SEVERE, "Subserver stop interrupted!", e); + Thread.currentThread().interrupt(); + } + } + + public void forceStop() { + try { + logger.log(Level.SEVERE, () -> serverName + " did not stop correctly, forcibly stopping!"); + process.destroyForcibly(); if (thread.isAlive()) thread.join(); } catch (InterruptedException e) { @@ -196,7 +219,7 @@ public class Subserver { protected void register() { if (Persistent.getInstance().getProxy().getServer(serverName).isPresent()) { SecurityException e = new SecurityException("Server already registered: " + serverName); - stop(); + sleep(); failureCallback.accept(e); throw e; } diff --git a/VelocityCore/src/de/steamwar/command/SWCommand.java b/VelocityCore/src/de/steamwar/command/SWCommand.java index 2fdf204f..e1e0c066 100644 --- a/VelocityCore/src/de/steamwar/command/SWCommand.java +++ b/VelocityCore/src/de/steamwar/command/SWCommand.java @@ -20,6 +20,7 @@ package de.steamwar.command; import com.velocitypowered.api.command.SimpleCommand; +import com.velocitypowered.api.command.SimpleCommand.Invocation; import de.steamwar.messages.Chatter; import de.steamwar.messages.Message; import de.steamwar.sql.UserPerm; @@ -91,11 +92,15 @@ public class SWCommand extends AbstractSWCommand { @Override public boolean hasPermission(Invocation invocation) { - return permission == null || Chatter.of(invocation.source()).user().perms().contains(permission); + return SWCommand.this.hasPermission(invocation); } }; } + protected boolean hasPermission(Invocation invocation) { + return permission == null || Chatter.of(invocation.source()).user().perms().contains(permission); + } + @Override public void unregister() { if (command == null) return; 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..087a121d 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/ServerStarter.java +++ b/VelocityCore/src/de/steamwar/velocitycore/ServerStarter.java @@ -201,8 +201,8 @@ public class ServerStarter { private void tempWorld(String template) { worldDir = TEMP_WORLD_PATH; - worldSetup = () -> copyWorld(node, template, worldDir + worldName); - worldCleanup = () -> SubserverSystem.deleteFolder(node, worldDir + worldName); + worldSetup = () -> copyWorld(node, template, new File(worldDir, worldName).getPath()); + worldCleanup = () -> SubserverSystem.deleteFolder(node, new File(worldDir, worldName).getPath()); } private void buildWithTemp(Player owner) { @@ -213,7 +213,7 @@ public class ServerStarter { if (startingBau(owner)) return false; Bauserver subserver = Bauserver.get(owner.getUniqueId()); - if (subserver != null && subserver.isStarted()) subserver.stop(); + if (subserver != null && subserver.isStarted()) subserver.sleep(); return !startingBau(owner); }; @@ -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)); } @@ -396,4 +396,4 @@ public class ServerStarter { } } -} \ No newline at end of file +} 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/advancements/Advancement.java b/VelocityCore/src/de/steamwar/velocitycore/advancements/Advancement.java new file mode 100644 index 00000000..b1a2befb --- /dev/null +++ b/VelocityCore/src/de/steamwar/velocitycore/advancements/Advancement.java @@ -0,0 +1,340 @@ +/* + * 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.velocitycore.advancements; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.connection.client.ConnectedPlayer; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; +import de.steamwar.messages.Chatter; +import de.steamwar.sql.SteamwarUser; +import io.netty.buffer.ByteBuf; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; +import net.kyori.adventure.text.Component; + +import java.util.*; +import java.util.function.BiFunction; +import java.util.function.Function; + +@RequiredArgsConstructor +public class Advancement { + + protected static final Map> values = new HashMap<>(); + + { + Advancements.all.add(this); + } + + protected final Map data = new HashMap<>(); + protected final Map value = new HashMap<>(); + + public Advancement.Data get(SteamwarUser user) { + return get(user, Data::new); + } + + public Advancement.Data get(SteamwarUser user, BiFunction function) { + if (data.containsKey(user)) return data.get(user); + return function.apply(this, user); + } + + private final String identifier; + private final Optional parent; + private final Display display; + + private final HidePolicy hidePolicy; + private final int total; + + private final Function progressCalculator; + + @Override + public String toString() { + StringBuilder st = new StringBuilder(); + st.append("Advancement("); + parent.ifPresent(advancement -> st.append(advancement.identifier).append("<-")); + st.append(identifier); + st.append(", total=").append(total); + st.append(")"); + return st.toString(); + } + + @RequiredArgsConstructor + @AllArgsConstructor + public static class Display { + private final Component title; + private final Component description; + private final String item; + private final FrameType frameType; + private Optional background = Optional.empty(); + private final float xCoord; + private final float yCoord; + + public enum FrameType { + TASK, + CHALLENGE, + GOAL + } + } + + public enum HidePolicy { + NEVER { + @Override + public boolean hidden(Data data) { + return false; + } + }, + NO_PROGRESS { + @Override + public boolean hidden(Data data) { + return data.progress == 0; + } + }, + PREVIOUS_UNFINISHED { + @Override + public boolean hidden(Data data) { + if (data.advancement.parent.isPresent()) { + Advancement parent = data.advancement.parent.get(); + Advancement.Data parentData = parent.get(data.user); + return parentData.progress != parentData.advancement.total; + } else { + return false; + } + } + }, + WITH_PREVIOUS { + @Override + public boolean hidden(Data data) { + if (data.advancement.parent.isPresent()) { + Advancement parent = data.advancement.parent.get(); + Advancement.Data parentData = parent.get(data.user); + return parentData.hidden; + } else { + return false; + } + } + }, + ; + + public abstract boolean hidden(Data data); + } + + public static class Value { + + @AllArgsConstructor + public static class Key { + public static final List keys = new ArrayList<>(); + + { + keys.add(this); + } + + private final Function valueFunction; + + private Advancement.Value get(SteamwarUser user) { + Key self = this; + return values.computeIfAbsent(user, __ -> new HashMap<>()).computeIfAbsent(self, __ -> { + Value data = new Advancement.Value(); + data.update(user, self); + return data; + }); + } + + public Function max(int neededValue) { + return user -> { + double value = get(user).value.doubleValue(); + if (value > neededValue) return Math.min(neededValue, 100); + return (int) (value / Math.max(neededValue / 100.0, 1)); + }; + } + + public Function reached(int neededValue) { + return user -> { + double value = get(user).value.doubleValue(); + return value >= neededValue ? 1 : 0; + }; + } + } + + @Getter + private T value; + + public void update(SteamwarUser user, Key key) { + this.value = key.valueFunction.apply(user); + } + } + + @ToString + public static class Data { + private final Advancement advancement; + private final SteamwarUser user; + + private int progress; + private boolean showToast = true; + private boolean hidden = false; + + public Data(Advancement advancement, SteamwarUser user) { + this.advancement = advancement; + advancement.data.put(user, this); + this.user = user; + this.progress = advancement.progressCalculator.apply(user); + checkHidden(); + checkFinished(); + new Packet(this, showToast).send(); + } + + public Data(Advancement advancement, SteamwarUser user, int progress) { + this.advancement = advancement; + advancement.data.put(user, this); + this.user = user; + this.progress = progress; + checkHidden(); + checkFinished(); + new Packet(this, showToast).send(); + } + + public void update() { + this.progress = advancement.progressCalculator.apply(user); + checkHidden(); + + new Packet(this, showToast).send(); + // Update Advancements that have this as parent + Advancements.getAll() + .stream() + .filter(advancement -> advancement.parent.filter(value -> value == this.advancement).isPresent()) + .map(advancement -> advancement.get(user)) + .forEach(Advancement.Data::update); + + checkFinished(); + } + + private void checkHidden() { + hidden = advancement.hidePolicy.hidden(this); + } + + private void checkFinished() { + if (progress == advancement.total) { + showToast = false; + } + } + + private void encodeAdvancement(ByteBuf byteBuf, ProtocolVersion protocolVersion, boolean showToast) { + ProtocolUtils.writeString(byteBuf, advancement.identifier); + if (advancement.parent.isPresent()) { + byteBuf.writeBoolean(true); + ProtocolUtils.writeString(byteBuf, advancement.parent.get().identifier); + } else { + byteBuf.writeBoolean(false); + } + + { // Display + byteBuf.writeBoolean(true); + new ComponentHolder(protocolVersion, advancement.display.title).write(byteBuf); + new ComponentHolder(protocolVersion, advancement.display.description).write(byteBuf); + { // Slot + ProtocolUtils.writeVarInt(byteBuf, 1); + int itemId = Items.values + .get(protocolVersion.name().replace("MINECRAFT_", "V_")) + .getAsJsonObject() + .get(advancement.display.item) + .getAsInt(); + ProtocolUtils.writeVarInt(byteBuf, itemId); + ProtocolUtils.writeVarInt(byteBuf, 0); + ProtocolUtils.writeVarInt(byteBuf, 0); + } + ProtocolUtils.writeVarInt(byteBuf, advancement.display.frameType.ordinal()); + if (advancement.display.background.isPresent()) { + byteBuf.writeInt(0x01 | (showToast ? 0x02 : 0x00) | (hidden ? 0x04 : 0x00)); + ProtocolUtils.writeString(byteBuf, advancement.display.background.get()); + } else { + byteBuf.writeInt((showToast ? 0x02 : 0x00) | (hidden ? 0x04 : 0x00)); + } + byteBuf.writeFloat(advancement.display.xCoord); + byteBuf.writeFloat(advancement.display.yCoord); + } + + ProtocolUtils.writeVarInt(byteBuf, advancement.total); + for (int i = 0; i < advancement.total; i++) { + ProtocolUtils.writeVarInt(byteBuf, 1); + ProtocolUtils.writeString(byteBuf, advancement.identifier + "_" + i); + } + + byteBuf.writeBoolean(false); // No Telemetry + } + + private void encodeProgress(ByteBuf byteBuf) { + ProtocolUtils.writeString(byteBuf, this.advancement.identifier); + ProtocolUtils.writeVarInt(byteBuf, advancement.total); + for (int i = 0; i < advancement.total; i++) { + ProtocolUtils.writeString(byteBuf, advancement.identifier + "_" + i); + if (i == advancement.total - 1 && advancement.total == progress) { + byteBuf.writeBoolean(true); + byteBuf.writeLong(new Date().getTime()); + } else if (i < progress) { + byteBuf.writeBoolean(true); + byteBuf.writeLong(0); + } else { + byteBuf.writeBoolean(false); + } + } + } + } + + protected record Packet(Data data, boolean showToast) implements MinecraftPacket { + public void send() { + Player player = Chatter.of(data.user).getPlayer(); + ((ConnectedPlayer) player).getConnection().write(this); + } + + @Override + public void decode(ByteBuf byteBuf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + throw new UnsupportedOperationException(); + } + + @Override + public void encode(ByteBuf byteBuf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + byteBuf.writeBoolean(false); // Clear + + if (!data.hidden) { + ProtocolUtils.writeVarInt(byteBuf, 1); + data.encodeAdvancement(byteBuf, protocolVersion, showToast); + ProtocolUtils.writeVarInt(byteBuf, 0); // No Advancements to remove + ProtocolUtils.writeVarInt(byteBuf, 1); + data.encodeProgress(byteBuf); + } else { + ProtocolUtils.writeVarInt(byteBuf, 0); // No Advancements to update + ProtocolUtils.writeVarInt(byteBuf, 1); + ProtocolUtils.writeString(byteBuf, data.advancement.identifier); + ProtocolUtils.writeVarInt(byteBuf, 0); // No Advancements Progress to update + } + + byteBuf.writeBoolean(true); // Show Advancements + } + + @Override + public boolean handle(MinecraftSessionHandler minecraftSessionHandler) { + return false; + } + } +} diff --git a/VelocityCore/src/de/steamwar/velocitycore/advancements/Advancements.java b/VelocityCore/src/de/steamwar/velocitycore/advancements/Advancements.java new file mode 100644 index 00000000..4742d266 --- /dev/null +++ b/VelocityCore/src/de/steamwar/velocitycore/advancements/Advancements.java @@ -0,0 +1,315 @@ +/* + * 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.velocitycore.advancements; + +import de.steamwar.messages.Chatter; +import de.steamwar.persistent.Storage; +import de.steamwar.sql.CheckedSchematic; +import de.steamwar.sql.EventFight; +import de.steamwar.sql.FightPlayer; +import lombok.Getter; +import lombok.experimental.UtilityClass; +import net.kyori.adventure.text.Component; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@UtilityClass +public class Advancements { + + @Getter + static final List all = new ArrayList<>(); + + @Getter + private static final List playtime = new ArrayList<>(); + + public static final Advancement.Value.Key PLAY_TIME_KEY = new Advancement.Value.Key<>(user -> { + double playtime = user.getOnlinetime(); + playtime += Instant.now().getEpochSecond() - Storage.sessions.get(Chatter.of(user).getPlayer()).toInstant().getEpochSecond(); + playtime /= 60d * 60d; + return playtime; + }); + + public static final Advancement.Value.Key FIGHT_COUNT = new Advancement.Value.Key<>(user -> { + return FightPlayer.countFights(user.getId()); + }); + + public static final Advancement.Value.Key FIGHT_COUNT_WAR_GEAR = new Advancement.Value.Key<>(user -> { + return FightPlayer.countFights(user.getId(), "WarGear"); + }); + + public static final Advancement.Value.Key FIGHT_COUNT_MINI_WAR_GEAR = new Advancement.Value.Key<>(user -> { + return FightPlayer.countFights(user.getId(), "MiniWarGear"); + }); + + public static final Advancement.Value.Key FIGHT_COUNT_WAR_SHIP = new Advancement.Value.Key<>(user -> { + return FightPlayer.countFights(user.getId(), "WarShip"); + }); + + public static final Advancement.Value.Key EVENT_FIGHT_COUNT = new Advancement.Value.Key<>(user -> { + return EventFight.countEventFights(user); + }); + + public static final Advancement.Value.Key EVENT_FIGHT_FIRST_PLACE_COUNT = new Advancement.Value.Key<>(user -> { + return EventFight.countPlacement(user, 1); + }); + + public static final Advancement.Value.Key EVENT_FIGHT_SECOND_PLACE_COUNT = new Advancement.Value.Key<>(user -> { + return EventFight.countPlacement(user, 2); + }); + + public static final Advancement.Value.Key EVENT_FIGHT_THIRDPLACE_COUNT = new Advancement.Value.Key<>(user -> { + return EventFight.countPlacement(user, 3); + }); + + public static final Advancement.Value.Key CHECKED_SCHEMATIC_COUNT = new Advancement.Value.Key<>(user -> { + return CheckedSchematic.countChecked(user); + }); + + public static final Advancement.Value.Key ACCEPTED_SCHEMATIC_COUNT = new Advancement.Value.Key<>(user -> { + return CheckedSchematic.countAccepted(user); + }); + + public static final Advancement.Value.Key ACCEPTED_SCHEMATIC_COUNT_WAR_GEAR = new Advancement.Value.Key<>(user -> { + return CheckedSchematic.countAccepted(user, "WarGear"); + }); + + public static final Advancement.Value.Key ACCEPTED_SCHEMATIC_COUNT_MINI_WAR_GEAR = new Advancement.Value.Key<>(user -> { + return CheckedSchematic.countAccepted(user, "MiniWarGear"); + }); + + public static final Advancement.Value.Key ACCEPTED_SCHEMATIC_COUNT_WAR_SHIP = new Advancement.Value.Key<>(user -> { + return CheckedSchematic.countAccepted(user, "WarShip"); + }); + + public static final Advancement ROOT = new Advancement( + "steamwar:advancements/root", + Optional.empty(), + new Advancement.Display( + Component.text("SteamWar"), + Component.text("Join SteamWar for the first time!"), + "cactus_flower", + Advancement.Display.FrameType.CHALLENGE, + Optional.of("minecraft:gui/advancements/backgrounds/adventure"), + 0f, + 3f + ), + Advancement.HidePolicy.NEVER, + 1, + user -> 1 + ); + + static { + Advancement previous = ROOT; + int[] playTimes = new int[]{1, 10, 100, 500, 1000, 2500, 5000, 7500, 10000, 15000, 20000}; + for (int i = 0; i < playTimes.length; i++) { + int neededPlayTime = playTimes[i]; + previous = new Advancement( + "steamwar:advancements/playtime_" + neededPlayTime + "_hour", + Optional.of(previous), + new Advancement.Display( + Component.text("Play " + neededPlayTime + " Hour" + (neededPlayTime > 1 ? "s" : "")), + Component.text("Play " + neededPlayTime + " hour" + (neededPlayTime > 1 ? "s" : "") + " on SteamWar"), + "clock", + Advancement.Display.FrameType.TASK, + i + 1f, + 3f + ), + Advancement.HidePolicy.PREVIOUS_UNFINISHED, + Math.min(neededPlayTime, 100), + PLAY_TIME_KEY.max(neededPlayTime) + ); + playtime.add(previous); + } + } + + static { + Advancement previous = ROOT; + int[] fightCounts = new int[]{1, 10, 50, 100, 200, 500, 1000, 2500, 5000, 7500, 10000, 15000, 20000}; + for (int i = 0; i < fightCounts.length; i++) { + int fightCount = fightCounts[i]; + previous = new Advancement( + "steamwar:advancements/fights_" + fightCount, + Optional.of(previous), + new Advancement.Display( + Component.text(fightCount + " Fight" + (fightCount > 1 ? "s" : "")), + Component.text(fightCount + " Fight" + (fightCount > 1 ? "s" : "")), + "iron_sword", + Advancement.Display.FrameType.TASK, + i + 1f, + 4f + ), + Advancement.HidePolicy.PREVIOUS_UNFINISHED, + Math.min(fightCount, 100), + FIGHT_COUNT.max(fightCount) + ); + + if (i == 0) { + fightsPerType(previous, 5f, "WarGear", FIGHT_COUNT_WAR_GEAR, "stone_bricks"); + fightsPerType(previous, 6f, "MiniWarGear", FIGHT_COUNT_MINI_WAR_GEAR, "stone_brick_slab"); + fightsPerType(previous, 7f, "WarShip", FIGHT_COUNT_WAR_SHIP, "dark_oak_boat"); + } + } + } + + private static void fightsPerType(Advancement previous, float yCoord, String type, Advancement.Value.Key typeKey, String item) { + int[] fightCounts = new int[]{1, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}; + for (int i = 0; i < fightCounts.length; i++) { + int fightCount = fightCounts[i]; + previous = new Advancement( + "steamwar:advancements/fights_" + type + "_" + fightCount, + Optional.of(previous), + new Advancement.Display( + Component.text(type + " " + fightCount + " Fight" + (fightCount > 1 ? "s" : "")), + Component.text(type + " " + fightCount + " Fight" + (fightCount > 1 ? "s" : "")), + item, + Advancement.Display.FrameType.TASK, + i + 2f, + yCoord + ), + i == 0 ? Advancement.HidePolicy.WITH_PREVIOUS : Advancement.HidePolicy.PREVIOUS_UNFINISHED, + Math.min(fightCount, 100), + typeKey.max(fightCount) + ); + } + } + + static { + Advancement previous = ROOT; + int[] eventFightCounts = new int[]{1, 5, 10, 15, 25, 50, 100, 150, 200, 250}; + for (int i = 0; i < eventFightCounts.length; i++) { + int eventFightCount = eventFightCounts[i]; + previous = new Advancement( + "steamwar:advancements/event_fights_" + eventFightCount, + Optional.of(previous), + new Advancement.Display( + Component.text(eventFightCount + " Event-Fight" + (eventFightCount > 1 ? "s" : "")), + Component.text(eventFightCount + " Event-Fight" + (eventFightCount > 1 ? "s" : "")), + "golden_sword", + Advancement.Display.FrameType.TASK, + i + 1f, + 8f + ), + Advancement.HidePolicy.PREVIOUS_UNFINISHED, + Math.min(eventFightCount, 100), + EVENT_FIGHT_COUNT.max(eventFightCount) + ); + + if (i == 0) { + placementsCounts(previous, 9f, 1, "gold_block", EVENT_FIGHT_FIRST_PLACE_COUNT, Advancement.Display.FrameType.CHALLENGE); + placementsCounts(previous, 10f, 2, "iron_block", EVENT_FIGHT_SECOND_PLACE_COUNT, Advancement.Display.FrameType.GOAL); + placementsCounts(previous, 11f, 3, "copper_block", EVENT_FIGHT_THIRDPLACE_COUNT, Advancement.Display.FrameType.TASK); + } + } + } + + private static void placementsCounts(Advancement previous, float yCoord, int placement, String item, Advancement.Value.Key typeKey, Advancement.Display.FrameType frameType) { + for (int placementCount = 1; placementCount <= 10; placementCount++) { + int finalPlacementCount = placementCount; + previous = new Advancement( + "steamwar:advancements/event_placement_" + placement + "_" + placementCount, + Optional.of(previous), + new Advancement.Display( + Component.text(placementCount + "x " + placement + ". Place in Event"), + Component.text(""), + item, + frameType, + 2f + (placementCount - 1f), + yCoord + ), + placementCount == 1 ? Advancement.HidePolicy.WITH_PREVIOUS : Advancement.HidePolicy.PREVIOUS_UNFINISHED, + 1, + typeKey.reached(placementCount) + ); + } + } + + static { + Advancement previous = ROOT; + int[] checkedCounts = new int[]{1, 10, 100, 250, 500, 750, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000}; + for (int i = 0; i < checkedCounts.length; i++) { + int checkedCount = checkedCounts[i]; + previous = new Advancement( + "steamwar:advancements/checked_" + checkedCount, + Optional.of(previous), + new Advancement.Display( + Component.text(checkedCount + " Check Session" + (checkedCount > 1 ? "s" : "")), + Component.text(checkedCount + " Check Session" + (checkedCount > 1 ? "s" : "")), + "paper", + Advancement.Display.FrameType.TASK, + i + 1f, + 0f + ), + i == 0 ? Advancement.HidePolicy.NO_PROGRESS : Advancement.HidePolicy.PREVIOUS_UNFINISHED, + Math.min(checkedCount, 100), + CHECKED_SCHEMATIC_COUNT.max(checkedCount) + ); + } + } + + static { + Advancement previous = ROOT; + int[] acceptedCounts = new int[]{1, 5, 10, 15, 25, 50, 100, 150, 200, 250, 500, 750, 1000}; + for (int i = 0; i < acceptedCounts.length; i++) { + int acceptedCount = acceptedCounts[i]; + previous = new Advancement( + "steamwar:advancements/accepted_" + acceptedCount, + Optional.of(previous), + new Advancement.Display( + Component.text(acceptedCount + " Accepted Schematic" + (acceptedCount > 1 ? "s" : "")), + Component.text(acceptedCount + " Accepted Schematic" + (acceptedCount > 1 ? "s" : "")), + "cauldron", + Advancement.Display.FrameType.TASK, + i + 1f, + 2f + ), + Advancement.HidePolicy.PREVIOUS_UNFINISHED, + Math.min(acceptedCount, 100), + ACCEPTED_SCHEMATIC_COUNT.max(acceptedCount) + ); + + if (i == 0) { + acceptedPerType(previous, 2f, "WarGear", ACCEPTED_SCHEMATIC_COUNT_WAR_GEAR, "end_stone_bricks"); + acceptedPerType(previous, 3f, "MiniWarGear", ACCEPTED_SCHEMATIC_COUNT_MINI_WAR_GEAR, "end_stone_brick_slab"); + acceptedPerType(previous, 4f, "WarShip", ACCEPTED_SCHEMATIC_COUNT_WAR_SHIP, "oak_boat"); + } + } + } + + private static void acceptedPerType(Advancement previous, float xCoord, String type, Advancement.Value.Key typeKey, String item) { + new Advancement( + "steamwar:advancements/accepted_" + type, + Optional.of(previous), + new Advancement.Display( + Component.text(type + " Accepted"), + Component.text(""), + item, + Advancement.Display.FrameType.GOAL, + xCoord, + 1f + ), + Advancement.HidePolicy.WITH_PREVIOUS, + 1, + typeKey.reached(1) + ); + } +} diff --git a/VelocityCore/src/de/steamwar/velocitycore/advancements/AdvancementsManager.java b/VelocityCore/src/de/steamwar/velocitycore/advancements/AdvancementsManager.java new file mode 100644 index 00000000..8b09f8c7 --- /dev/null +++ b/VelocityCore/src/de/steamwar/velocitycore/advancements/AdvancementsManager.java @@ -0,0 +1,99 @@ +/* + * 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.velocitycore.advancements; + +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.connection.DisconnectEvent; +import com.velocitypowered.api.event.connection.PostLoginEvent; +import com.velocitypowered.api.event.player.ServerPostConnectEvent; +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.proxy.connection.client.ConnectedPlayer; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.protocol.StateRegistry; +import de.steamwar.linkage.Linked; +import de.steamwar.sql.SteamwarUser; +import de.steamwar.velocitycore.listeners.BasicListener; +import it.unimi.dsi.fastutil.objects.Object2IntMap; + +import java.lang.reflect.Field; +import java.util.Optional; + +// @Linked +public class AdvancementsManager extends BasicListener { + + private static SelectAdvancementTabPacket selectAdvancementTabPacket; + + static { + selectAdvancementTabPacket = new SelectAdvancementTabPacket(Optional.of("steamwar:advancements/root")); + + registerPacketId(ProtocolVersion.MINECRAFT_1_21_9, 0x53, 0x80); + registerPacketId(ProtocolVersion.MINECRAFT_1_21_7, 0x4E, 0x7B); + registerPacketId(ProtocolVersion.MINECRAFT_1_21_6, 0x4E, 0x7B); + registerPacketId(ProtocolVersion.MINECRAFT_1_21_5, 0x4E, 0x7B); + registerPacketId(ProtocolVersion.MINECRAFT_1_21_4, 0x4F, 0x7B); + } + + private static void registerPacketId(ProtocolVersion version, int selectAdvancementTabPacket, int advancementPacket) { + try { + StateRegistry.PacketRegistry.ProtocolRegistry registry = StateRegistry.PLAY.getProtocolRegistry(ProtocolUtils.Direction.CLIENTBOUND, version); + Field field = StateRegistry.PacketRegistry.ProtocolRegistry.class.getDeclaredField("packetClassToId"); + field.setAccessible(true); + Object2IntMap> map = (Object2IntMap) field.get(registry); + map.put(SelectAdvancementTabPacket.class, selectAdvancementTabPacket); + map.put(Advancement.Packet.class, advancementPacket); + } catch (Exception e) { + // Ignore + } + } + + @Subscribe(priority = -1000) + public void onPostLogin(PostLoginEvent event) { + sendAdvancements(event.getPlayer()); + } + + @Subscribe(priority = -1000) + public void onServerPostConnect(ServerPostConnectEvent event) { + sendAdvancements(event.getPlayer()); + } + + private void sendAdvancements(Player player) { + // Only enable for 1.21.4 or higher + if (player.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_21_4)) { + return; + } + + ((ConnectedPlayer) player).getConnection().write(selectAdvancementTabPacket); + SteamwarUser user = SteamwarUser.get(player.getUniqueId()); + for (Advancement advancement : Advancements.getAll()) { + advancement.get(user).update(); + } + } + + @Subscribe + public void onDisconnect(DisconnectEvent event) { + SteamwarUser user = SteamwarUser.get(event.getPlayer().getUniqueId()); + for (Advancement advancement : Advancements.getAll()) { + advancement.data.remove(user); + } + Advancement.values.remove(user); + } +} diff --git a/VelocityCore/src/de/steamwar/velocitycore/advancements/SelectAdvancementTabPacket.java b/VelocityCore/src/de/steamwar/velocitycore/advancements/SelectAdvancementTabPacket.java new file mode 100644 index 00000000..b344f62d --- /dev/null +++ b/VelocityCore/src/de/steamwar/velocitycore/advancements/SelectAdvancementTabPacket.java @@ -0,0 +1,55 @@ +/* + * 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.velocitycore.advancements; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import lombok.AllArgsConstructor; + +import java.util.Optional; + +@AllArgsConstructor +public class SelectAdvancementTabPacket implements MinecraftPacket { + + private Optional identifier; + + @Override + public void decode(ByteBuf byteBuf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + throw new UnsupportedOperationException("Packet is not implemented"); + } + + @Override + public void encode(ByteBuf byteBuf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + if (this.identifier.isPresent()) { + byteBuf.writeBoolean(true); + ProtocolUtils.writeString(byteBuf, this.identifier.get()); + } else { + byteBuf.writeBoolean(false); + } + } + + @Override + public boolean handle(MinecraftSessionHandler minecraftSessionHandler) { + return false; + } +} diff --git a/VelocityCore/src/de/steamwar/velocitycore/commands/CheckCommand.java b/VelocityCore/src/de/steamwar/velocitycore/commands/CheckCommand.java index 637218e3..b0183aa5 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/commands/CheckCommand.java +++ b/VelocityCore/src/de/steamwar/velocitycore/commands/CheckCommand.java @@ -19,6 +19,7 @@ package de.steamwar.velocitycore.commands; +import com.velocitypowered.api.command.SimpleCommand; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ServerConnection; import de.steamwar.command.SWCommand; @@ -62,61 +63,89 @@ public class CheckCommand extends SWCommand { public static Message getWaitTime(SchematicNode schematic) { long waitedMillis = Timestamp.from(Instant.now()).getTime() - schematic.getLastUpdate().getTime(); - String ce = waitedMillis > 86400000 ? "c" : "e"; - String color = waitedMillis > 14400000 ? ce : "a"; + String color; + if (waitedMillis > 48L * 60 * 60 * 1000) color = "4"; + else if (waitedMillis > 24L * 60 * 60 * 1000) color = "c"; + else if (waitedMillis > 12L * 60 * 60 * 1000) color = "6"; + else if (waitedMillis > 4L * 60 * 60 * 1000) color = "e"; + else color = "a"; long hours = waitedMillis / 3600000; long minutes = (waitedMillis - hours * 3600000) / 60000; return new Message("CHECK_LIST_WAIT", color, hours, (minutes < 10) ? "0" + minutes : minutes); } public CheckCommand() { - super("check", UserPerm.CHECK); + super("check"); + VelocityCore.schedule(() -> Chatter.allStream().forEach(CheckCommand::sendReminder)).delay(10, TimeUnit.MINUTES).repeat(10, TimeUnit.MINUTES).schedule(); + } - VelocityCore.schedule(() -> sendReminder(Chatter.serverteam())).repeat(10, TimeUnit.MINUTES).schedule(); + @Override + protected boolean hasPermission(SimpleCommand.Invocation invocation) { + SteamwarUser user = Chatter.of(invocation.source()).user(); + if (user.perms().contains(UserPerm.CHECK)) return true; + return GameModeConfig.getAll() + .stream() + .filter(GameModeConfig::isActive) + .anyMatch(gameMode -> gameMode.Checkers.contains(user.getId())); + } + + private static Map getSchematics(SteamwarUser user) { + Map map = new HashMap<>(); + for (SchematicNode schematicNode : getSchemsToCheck()) { + if (!mayCheck(user, schematicNode)) continue; + CheckSession checkSession = currentSchems.get(schematicNode.getId()); + map.put(schematicNode, checkSession == null ? null : checkSession.checker.user()); + } + return map; + } + + private static boolean mayCheck(SteamwarUser user, SchematicNode schematic) { + GameModeConfig gameModeConfig = ArenaMode.getBySchemType(schematic.getSchemtype()); + if (gameModeConfig == null) gameModeConfig = GameModeConfig.getDefaults(); + if (user.hasPerm(UserPerm.ADMINISTRATION)) return true; + if (gameModeConfig.Checkers.isEmpty() && user.hasPerm(UserPerm.CHECK)) return true; + return gameModeConfig.Checkers.contains(user.getId()); } public static void sendReminder(Chatter chatter) { - List schematics = getSchemsToCheck(); - if (schematics.size() == currentCheckers.size()) return; - - chatter.system("CHECK_REMINDER", new Message("CHECK_REMINDER_HOVER"), ClickEvent.runCommand("/check list"), schematics.size() - currentCheckers.size()); + Map schematics = getSchematics(chatter.user()); + if (schematics.isEmpty()) return; + long needsChecking = schematics.entrySet().stream().filter(entry -> entry.getValue() == null).count(); + if (needsChecking == 0) return; + chatter.system("CHECK_REMINDER", new Message("CHECK_REMINDER_HOVER"), ClickEvent.runCommand("/check list"), needsChecking); } @Register(value = "list", description = "CHECK_HELP_LIST") public void list(Chatter sender) { - List schematicList = getSchemsToCheck(); + Map schematics = getSchematics(sender.user()); - sender.system("CHECK_LIST_HEADER", schematicList.size()); + sender.system("CHECK_LIST_HEADER", schematics.size()); - for (SchematicNode schematic : schematicList) { - GameModeConfig gameModeConfig = ArenaMode.getBySchemType(schematic.getSchemtype()); - if (gameModeConfig == null) gameModeConfig = GameModeConfig.getDefaults(); - CheckSession current = currentSchems.get(schematic.getId()); - ClickEvent clickEvent = null; - Message hoverMessage = null; - if (gameModeConfig.Checkers.isEmpty() || gameModeConfig.Checkers.contains(sender.user().getId())) { - if (current == null) { - clickEvent = ClickEvent.runCommand("/check schematic " + schematic.getId()); - hoverMessage = new Message("CHECK_LIST_TO_CHECK_HOVER"); - } else { - clickEvent = ClickEvent.runCommand("/join " + current.checker.user().getUserName()); - hoverMessage = new Message("CHECK_LIST_CHECKING_HOVER"); - } - } - - if (current == null) { - sender.prefixless("CHECK_LIST_TO_CHECK", - hoverMessage, - clickEvent, - getWaitTime(schematic), - schematic.getSchemtype().getKuerzel(), SteamwarUser.byId(schematic.getOwner()).getUserName(), schematic.getName()); + for (Map.Entry entry : schematics.entrySet()) { + String message; + ClickEvent clickEvent; + Message hoverMessage; + String checker; + if (entry.getValue() == null) { + message = "CHECK_LIST_TO_CHECK"; + clickEvent = ClickEvent.runCommand("/check schematic " + entry.getKey().getId()); + hoverMessage = new Message("CHECK_LIST_TO_CHECK_HOVER"); + checker = ""; } else { - sender.prefixless("CHECK_LIST_CHECKING", - hoverMessage, - clickEvent, - getWaitTime(schematic), - schematic.getSchemtype().getKuerzel(), SteamwarUser.byId(schematic.getOwner()).getUserName(), schematic.getName(), current.checker.user().getUserName()); + message = "CHECK_LIST_CHECKING"; + clickEvent = ClickEvent.runCommand("/join " + entry.getValue().getUserName()); + hoverMessage = new Message("CHECK_LIST_CHECKING_HOVER"); + checker = entry.getValue().getUserName(); } + + sender.prefixless(message, + hoverMessage, + clickEvent, + getWaitTime(entry.getKey()), + entry.getKey().getSchemtype().getKuerzel(), + SteamwarUser.byId(entry.getKey().getOwner()).getUserName(), + entry.getKey().getName(), + checker); } } @@ -142,8 +171,7 @@ public class CheckCommand extends SWCommand { } int playerTeam = sender.user().hasPerm(UserPerm.MODERATION) ? 0 : sender.user().getTeam(); - // Ignore 795 SteamWar Team - if (playerTeam != 0 && playerTeam != 795 && SteamwarUser.byId(schem.getOwner()).getTeam() == playerTeam) { + if (playerTeam != 0 && SteamwarUser.byId(schem.getOwner()).getTeam() == playerTeam) { sender.system("CHECK_SCHEMATIC_OWN_TEAM"); return; } @@ -209,11 +237,6 @@ public class CheckCommand extends SWCommand { return schematicList; } - public static String getChecker(SchematicNode schematic) { - if (currentSchems.get(schematic.getId()) == null) return null; - return currentSchems.get(schematic.getId()).checker.user().getUserName(); - } - private static boolean notChecking(Player player) { if (!isChecking(player)) { Chatter.of(player).system("CHECK_NOT_CHECKING"); 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/VelocityCore/src/de/steamwar/velocitycore/listeners/ConnectionListener.java b/VelocityCore/src/de/steamwar/velocitycore/listeners/ConnectionListener.java index b8bffa6e..021f65d0 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/listeners/ConnectionListener.java +++ b/VelocityCore/src/de/steamwar/velocitycore/listeners/ConnectionListener.java @@ -35,6 +35,9 @@ import de.steamwar.sql.CheckedSchematic; import de.steamwar.sql.SchematicType; import de.steamwar.sql.SteamwarUser; import de.steamwar.sql.UserPerm; +import de.steamwar.velocitycore.VelocityCore; +import de.steamwar.velocitycore.advancements.Advancement; +import de.steamwar.velocitycore.advancements.Advancements; import de.steamwar.velocitycore.commands.*; import de.steamwar.velocitycore.discord.DiscordBot; import de.steamwar.velocitycore.discord.util.DiscordRanks; @@ -45,6 +48,7 @@ import net.kyori.adventure.text.event.ClickEvent; import java.util.HashSet; import java.util.Set; import java.util.UUID; +import java.util.concurrent.TimeUnit; @Linked public class ConnectionListener extends BasicListener { @@ -80,8 +84,7 @@ public class ConnectionListener extends BasicListener { Player player = event.getPlayer(); SteamwarUser user = SteamwarUser.get(player.getUniqueId()); Chatter chatter = Chatter.of(player); - - if (user.hasPerm(UserPerm.CHECK)) CheckCommand.sendReminder(chatter); + CheckCommand.sendReminder(chatter); for (Subserver subserver : Subserver.getServerList()) { if (Subserver.isArena(subserver)) { @@ -102,8 +105,12 @@ public class ConnectionListener extends BasicListener { } if (newPlayers.contains(player.getUniqueId())) { + Advancements.ROOT.get(user, (advancement, __) -> new Advancement.Data(advancement, user, 0)); Chatter.broadcast().system("JOIN_FIRST", player); newPlayers.remove(player.getUniqueId()); + VelocityCore.schedule(() -> { + Advancements.ROOT.get(user).update(); + }).delay(1, TimeUnit.SECONDS).schedule(); } if (!StreamingCommand.isNotStreaming(user)) { 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 94% rename from buildSrc/src/steamwar.devserver.gradle rename to buildSrc/src/main/groovy/steamwar.devserver.gradle index af6d224e..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 @@ -64,6 +64,10 @@ class DevServer extends DefaultTask { @Optional Boolean forceUpgrade = null + @Input + @Optional + String worldName = null + DevServer() { super() doFirst { @@ -83,13 +87,16 @@ class DevServer extends DefaultTask { } } - 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) - if (worldName == null) { - throw new GradleException("Please supply the 'worldName' in a 'steamwar.properties' files either in this project dir or any parent project!") - } if (host == null) { throw new GradleException("Please supply the 'host' in a 'steamwar.properties' files either in this project dir or any parent project!") } @@ -115,9 +122,6 @@ class DevServer extends DefaultTask { @Internal Boolean running = true - @Internal - String worldName = null - class Finalizer extends DefaultTask { Finalizer() { @@ -245,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") 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()}") 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..14419caa 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -118,7 +118,6 @@ dependencyResolutionManagement { library("nms", "de.steamwar:spigot:1.21.6") library("axiom", "de.steamwar:axiompaper:RELEASE") - library("worldedit", "com.sk89q.worldedit:worldedit-bukkit:7.3.16") library("fawe", "de.steamwar:fastasyncworldedit:1.21") library("velocity", "de.steamwar:velocity:RELEASE") @@ -162,6 +161,10 @@ dependencyResolutionManagement { } } +include( + "AccessWidener" +) + include( "BauSystem", "BauSystem:BauSystem_Main",