Compare commits

..

8 Commits

Author SHA1 Message Date
Chaoscaot 997292d58e Merge branch 'main' into swui-v2
Pull Request Build / Build (pull_request) Successful in 1m54s
2026-06-10 21:43:25 +02:00
Chaoscaot 8050f51424 Cleanup for Merge
Pull Request Build / Build (pull_request) Successful in 1m40s
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-06-10 21:43:02 +02:00
Chaoscaot a6681ffaf6 Add Top-Level State and more Utils
Pull Request Build / Build (pull_request) Successful in 1m21s
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-06-10 16:07:37 +02:00
Chaoscaot a23530074c Merge remote-tracking branch 'origin/swui-v2' into swui-v2
Pull Request Build / Build (pull_request) Successful in 1m45s
2026-06-10 09:28:24 +02:00
Chaoscaot e88a4c624b Merge branch 'main' into swui-v2 2026-06-10 09:28:16 +02:00
Chaoscaot 105beaf7a6 Merge branch 'main' into swui-v2
Pull Request Build / Build (pull_request) Successful in 1m35s
2026-06-10 09:27:59 +02:00
Chaoscaot 5f7ab3cd7b Updates
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-06-10 09:26:04 +02:00
Chaoscaot 5ce69e6b7a SWUI V2
Pull Request Build / Build (pull_request) Successful in 1m19s
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-06-10 00:36:18 +02:00
106 changed files with 1545 additions and 2681 deletions
-1
View File
@@ -51,7 +51,6 @@ 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"
-45
View File
@@ -1,45 +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 <https://www.gnu.org/licenses/>.
*/
plugins {
`java-library`
alias(libs.plugins.shadow)
}
dependencies {
implementation("org.ow2.asm:asm:9.7")
implementation("org.ow2.asm:asm-commons:9.7")
}
tasks.shadowJar {
manifest {
attributes(
"Manifest-Version" to "1.0",
"Build-Jdk-Spec" to "21",
"Main-Class" to "de.steamwar.Main",
"Premain-Class" to "de.steamwar.Agent",
"Can-Retransform-Classes" to "true",
"Can-Redefine-Classes" to "true",
)
}
}
tasks.build {
dependsOn(tasks.shadowJar)
}
@@ -1,49 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package de.steamwar;
/**
* A single parsed line from a .accesswidener file.
* <p>
* 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);
}
}
@@ -1,104 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
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.
* <p>
* Supported format:
* <pre>
* 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
* </pre>
*/
public final class AccessWidenerParser {
private AccessWidenerParser() {
}
public static List<AccessWidenerEntry> parse(InputStream in) throws IOException {
List<AccessWidenerEntry> 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;
};
}
}
@@ -1,75 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
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.
* <p>
* At JVM startup: java -javaagent:paper-access-widener-agent.jar -jar server.jar
* <p>
* On attach the agent:
* <ol>
* <li>Find all .jar files inside the plugins folder</li>
* <li>Scan all found jars for *.accesswidener resources</li>
* <li>Transform any class during loading</li>
* </ol>
*/
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<AccessWidenerEntry> 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.");
}
}
@@ -1,83 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
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<AccessWidenerEntry> entries;
/** Pre-computed set of targeted internal names for fast filtering. */
private final Set<String> targets;
private final Set<String> targetsPublicConstructor;
public ClassPatcher(List<AccessWidenerEntry> 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;
}
}
}
@@ -1,125 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
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<AccessWidenerEntry> entries;
private final boolean appendPublicConstructor;
public ClassTransformer(ClassVisitor cv, String internalName, List<AccessWidenerEntry> 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, "<init>", "()V", null, null);
methodVisitor.visitCode();
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
methodVisitor.visitMethodInsn(
Opcodes.INVOKESPECIAL,
"java/lang/Object",
"<init>",
"()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;
}
}
@@ -1,125 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
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 <input.jar> <output.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 <input.jar> <output.jar> [*.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<AccessWidenerEntry> 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<AccessWidenerEntry> 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());
}
}
}
@@ -1,60 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
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<AccessWidenerEntry> findAndParseAccessWideners(Path jarPath) throws IOException {
List<AccessWidenerEntry> results = new ArrayList<>();
try (ZipFile zip = new ZipFile(jarPath.toFile())) {
Enumeration<? extends ZipEntry> entries = zip.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
if (entry.isDirectory() || !entry.getName().endsWith(".accesswidener")) continue;
try (InputStream in = zip.getInputStream(entry)) {
results.addAll(AccessWidenerParser.parse(in));
} catch (IOException e) {
LOG.warning("[AccessWidener] Failed to parse " + entry.getName()
+ " in " + jarPath.getFileName() + ": " + e.getMessage());
}
}
}
return results;
}
}
@@ -1,44 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package de.steamwar;
import java.lang.instrument.ClassFileTransformer;
import java.security.ProtectionDomain;
import java.util.List;
/**
* Transforms class bytecode to apply access widening rules.
* <p>
* 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<AccessWidenerEntry> entries) {
patcher = new ClassPatcher(entries);
}
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
return patcher.patch(className, classfileBuffer);
}
}
+2 -6
View File
@@ -18,8 +18,7 @@
*/
plugins {
steamwar.kotlin
widener
steamwar.java
}
tasks.compileJava {
@@ -35,6 +34,7 @@ dependencies {
compileOnly(libs.classindex)
annotationProcessor(libs.classindex)
compileOnly(project(":SpigotCore", "default"))
compileOnly(project(":KotlinCore", "default"))
compileOnly(libs.axiom)
compileOnly(libs.authlib)
@@ -48,7 +48,3 @@ dependencies {
implementation(libs.luaj)
implementation(files("$projectDir/../libs/YAPION-SNAPSHOT.jar"))
}
widener {
fromCatalog(libs.nms)
}
@@ -1,13 +0,0 @@
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
@@ -22,7 +22,6 @@ package de.steamwar.bausystem;
import de.steamwar.bausystem.config.BauServer;
import de.steamwar.bausystem.configplayer.Config;
import de.steamwar.bausystem.configplayer.ConfigConverter;
import de.steamwar.bausystem.features.cannonCore.CannonCoreRegistrationKt;
import de.steamwar.bausystem.features.gui.BauGUI;
import de.steamwar.bausystem.features.script.lua.SteamWarLuaPlugin;
import de.steamwar.bausystem.features.script.lua.libs.LuaLib;
@@ -136,8 +135,6 @@ public class BauSystem extends JavaPlugin implements Listener {
String identifier = BauServerInfo.getOwnerUser().getUUID().toString().replace("-", "");
WorldIdentifier.set("bau/" + Core.getVersion() + "/" + identifier);
CannonCoreRegistrationKt.register(this);
}
@EventHandler
@@ -1,64 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.cannonCore
import de.steamwar.bausystem.utils.ItemUtils
import de.steamwar.core.SWPlayer
import de.steamwar.inventory.SWItem
import org.bukkit.Material
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.player.PlayerItemHeldEvent
import org.bukkit.inventory.ItemStack
object CannonCoreWand: Listener {
val wandId = "CANNON_CORE_WAND"
val wandMaterial = Material.BREEZE_ROD
fun getWandItem(): ItemStack {
val title = "§eCannon Core Wand"
val lore = listOf(
"§eRight Click §8- §7Create a new cannon core"
)
val wand = SWItem(wandMaterial, title, lore, false, null)
val item = wand.itemStack
return ItemUtils.setItem(item, wandId)
}
fun isWandItem(itemStack: ItemStack): Boolean {
return ItemUtils.isItem(itemStack, wandId)
}
@EventHandler
fun onPlayerEquip(event: PlayerItemHeldEvent) {
val player = event.player
val swPlayer = SWPlayer.of(event.player)
val item = player.inventory.getItem(event.newSlot) ?: return
if(!isWandItem(item)) {
swPlayer.removeComponent(EmptyCannonCoreWandDisplay::class.java)
}
else {
EmptyCannonCoreWandDisplay(player)
}
}
}
@@ -1,45 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.cannonCore
import de.steamwar.core.SWPlayer
import de.steamwar.entity.REntityServer
import org.bukkit.entity.Player
class CannonCoresDisplay: SWPlayer.Component {
val displayServer = REntityServer()
val unlisten: () -> Unit
constructor(owner: Player) {
unlisten = CannonCore.activeCores.observe( { cores ->
displayServer.entities.forEach { it.die() }
cores.forEach { CannonCoreEntity(displayServer, it.location) { _, _ -> println("Handler clicked at ${it.location}") } }
})
}
override fun onMount(player: SWPlayer) {
displayServer.addPlayer(player.player)
}
override fun onUnmount(player: SWPlayer?) {
unlisten()
displayServer.close()
}
}
@@ -1,47 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.cannonCore
typealias Observer<T> = (currentValue: T) -> Unit
class Observable<T>(var value: T) {
private val observers = ArrayList<Observer<T>>()
fun set(value: T) {
val oldValue = this.value
this.value = value
observers.forEach { it.invoke(value) }
}
fun get(): T {
return value
}
fun observe(observer: Observer<T>): () -> Unit {
observers.add(observer)
observer(value)
return {removeObserver(observer)}
}
fun removeObserver(observer: Observer<T>) {
observers.remove(observer)
}
}
@@ -55,11 +55,19 @@ 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.*;
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.inventory.ItemStack;
import org.bukkit.util.Vector;
import java.util.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -32,6 +32,7 @@ import de.steamwar.linkage.Linked;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
@Linked
@@ -31,7 +31,6 @@ 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;
@@ -39,6 +38,7 @@ 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;
@@ -25,6 +25,7 @@ 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;
@@ -38,7 +38,6 @@ 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;
@@ -48,6 +47,7 @@ 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,7 +73,19 @@ public class BindCommand extends SWCommand implements Listener {
}
}
private static final CommandMap commandMap = ((CraftServer) Bukkit.getServer()).getCommandMap();
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 NamespacedKey KEY = SWUtils.getNamespaceKey("command");
@@ -21,6 +21,7 @@ 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;
@@ -29,6 +30,7 @@ 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;
@@ -101,8 +103,10 @@ public class NoClipCommand extends SWCommand implements Listener {
TinyProtocol.instance.addFilter(ServerboundSetCreativeModeSlotPacket.class, third);
}
private static final Reflection.Field<GameType> playerGameMode = Reflection.getField(ServerPlayerGameMode.class, GameType.class, 0);
private void setInternalGameMode(Player player, GameMode gameMode) {
((CraftPlayer) player).getHandle().gameMode.gameModeForPlayer = GameType.byId(gameMode.getValue());
playerGameMode.set(((CraftPlayer) player).getHandle().gameMode, GameType.byId(gameMode.getValue()));
}
@Register(help = true)
@@ -36,6 +36,8 @@ 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;
@@ -25,7 +25,6 @@ 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;
@@ -40,6 +39,7 @@ 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;
@@ -57,7 +57,7 @@ public class WorldEditListener implements Listener {
private static final Set<String> commands = new HashSet<>();
private static final Set<String> commandExclusions = new HashSet<>();
private static final String[] shortcutCommands = {"//1", "//2", "//90", "//-90", "//180", "//p", "//c", "//flopy", "//floppy", "//flopyp", "//floppyp", "//u", "//r", "//download", "/download"};
private static final String[] shortcutCommands = {"//1", "//2", "//90", "//-90", "//180", "//p", "//c", "//flopy", "//floppy", "//flopyp", "//floppyp", "//u", "//r"};
public static boolean isWorldEditCommand(String command) {
for (String shortcut : shortcutCommands) {
@@ -28,7 +28,6 @@ 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;
@@ -41,6 +40,7 @@ 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;
@@ -20,30 +20,28 @@
package de.steamwar.bausystem.utils;
import de.steamwar.bausystem.SWUtils;
import lombok.experimental.UtilityClass;
import org.bukkit.NamespacedKey;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
public final class ItemUtils {
@UtilityClass
public class ItemUtils {
private static final NamespacedKey ITEM_KEY = SWUtils.getNamespaceKey("bau_item");
private final NamespacedKey ITEM_KEY = SWUtils.getNamespaceKey("bau_item");
private ItemUtils() {
}
public static boolean isItem(ItemStack itemStack, String tag) {
public boolean isItem(ItemStack itemStack, String tag) {
String value = getTag(itemStack, ITEM_KEY);
return value != null && value.equals(tag);
}
public static ItemStack setItem(ItemStack itemStack, String tag) {
public void setItem(ItemStack itemStack, String tag) {
setTag(itemStack, ITEM_KEY, tag);
return itemStack;
}
public static String getTag(ItemStack itemStack, NamespacedKey key) {
public String getTag(ItemStack itemStack, NamespacedKey key) {
if (itemStack == null) {
return null;
}
@@ -58,7 +56,7 @@ public final class ItemUtils {
return container.get(key, PersistentDataType.STRING);
}
public static void setTag(ItemStack itemStack, NamespacedKey key, String value) {
public void setTag(ItemStack itemStack, NamespacedKey key, String value) {
if (itemStack == null) {
return;
}
@@ -19,6 +19,7 @@
package de.steamwar.bausystem.utils;
import de.steamwar.Reflection;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.experimental.UtilityClass;
@@ -85,6 +86,9 @@ 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
@@ -284,9 +288,8 @@ 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();
CraftBlockState craftBlockState = (CraftBlockState) blockState;
craftBlockState.position = new BlockPos(blockLocation.getBlockX(), blockLocation.getBlockY(), blockLocation.getBlockZ());
craftBlockState.world = (CraftWorld) blockLocation.getWorld();
positionAccessor.set(blockState, new BlockPos(blockLocation.getBlockX(), blockLocation.getBlockY(), blockLocation.getBlockZ()));
worldAccessor.set(blockState, blockLocation.getWorld());
}
if (blockData.getMaterial().isSolid()) {
@@ -20,6 +20,7 @@
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;
@@ -32,6 +33,7 @@ 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<Long> remainingSprintTicks = Reflection.getField(ServerTickRateManager.class, long.class, 0);
private boolean blockTpsPacket = true;
private int totalSteps;
@@ -119,7 +121,7 @@ public class TickManager implements Listener {
public long getRemainingTicks() {
if (isSprinting()) {
return manager.remainingSprintTicks;
return remainingSprintTicks.get(manager);
} else {
return manager.frozenTicksToRun();
}
@@ -19,7 +19,6 @@
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;
@@ -33,19 +32,16 @@ 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 com.sk89q.worldedit.world.World;
import de.steamwar.Reflection;
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
@@ -95,36 +91,17 @@ public class WorldEditUtils {
.getRegionSelector(BukkitAdapter.adapt(player.getWorld()));
return new Pair<>(regionSelector.getClass(), regionSelector.getVertices()
.stream()
.map(blockVector3 -> {
if (blockVector3 == null) {
return null;
} else {
return BukkitAdapter.adapt(player.getWorld(), blockVector3);
}
})
.map(blockVector3 -> blockVector3 == null ? null : adapt(player.getWorld(), blockVector3))
.collect(Collectors.toList()));
}
private static final Map<Class<? extends RegionSelector>, Function<World, RegionSelector>> constructors = new HashMap<>();
static {
constructors.put(CuboidRegionSelector.class, CuboidRegionSelector::new);
constructors.put(ExtendingCuboidRegionSelector.class, ExtendingCuboidRegionSelector::new);
constructors.put(Polygonal2DRegionSelector.class, Polygonal2DRegionSelector::new);
constructors.put(EllipsoidRegionSelector.class, EllipsoidRegionSelector::new);
constructors.put(SphereRegionSelector.class, SphereRegionSelector::new);
constructors.put(CylinderRegionSelector.class, CylinderRegionSelector::new);
constructors.put(ConvexPolyhedralRegionSelector.class, ConvexPolyhedralRegionSelector::new);
constructors.put(PolyhedralRegionSelector.class, PolyhedralRegionSelector::new);
}
public void setVertices(Player player, Class<? extends RegionSelector> clazz, List<Location> vertices) {
LocalSession localSession = WorldEdit.getInstance()
.getSessionManager()
.get(BukkitAdapter.adapt(player));
Function<World, RegionSelector> constructor = constructors.get(clazz);
if (constructor == null) return;
RegionSelector regionSelector = constructor.apply(BukkitAdapter.adapt(player.getWorld()));
Reflection.Constructor constructorInvoker = Reflection.getConstructor(clazz, com.sk89q.worldedit.world.World.class);
RegionSelector regionSelector = (RegionSelector) constructorInvoker.invoke(BukkitAdapter.adapt(player.getWorld()));
localSession.setRegionSelector(BukkitAdapter.adapt(player.getWorld()), regionSelector);
if (vertices.isEmpty()) return;
@@ -150,9 +127,13 @@ public class WorldEditUtils {
try {
BlockVector3 min = regionSelector.getRegion().getMinimumPoint();
BlockVector3 max = regionSelector.getRegion().getMaximumPoint();
return new Pair<>(BukkitAdapter.adapt(player.getWorld(), min), BukkitAdapter.adapt(player.getWorld(), max));
return new Pair<>(adapt(player.getWorld(), min), 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());
}
}
+1 -1
View File
@@ -4,7 +4,7 @@ version: "2.0"
depend: [ WorldEdit, SpigotCore ]
load: POSTWORLD
main: de.steamwar.bausystem.BauSystem
api-version: "1.21"
api-version: "1.13"
website: "https://steamwar.de"
description: "So unseriös wie wir sind: BauSystem nur besser."
+1 -1
View File
@@ -37,6 +37,6 @@ tasks.register<DevServer>("DevBau21") {
dependsOn(":SpigotCore:shadowJar")
dependsOn(":BauSystem:shadowJar")
dependsOn(":SchematicSystem:shadowJar")
dependsOn(":KotlinCore:shadowJar")
template = "Bau21"
debug = true
}
-1
View File
@@ -18,7 +18,6 @@ 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)
+11 -63
View File
@@ -14,11 +14,9 @@ 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 = """<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" packages="com.mojang.util">
@@ -71,42 +69,32 @@ class DevCommand : CliktCommand("dev") {
override fun run() {
val args = mutableListOf<String>()
var serverDir = resolveServerDirectory(server)
val serverDirectory = File(workingDir, server)
val serverDir =
if (serverDirectory.exists() && serverDirectory.isDirectory) serverDirectory else File(workingDir, server)
if (isVelocity(server)) {
runServer(
args, jvmArgs, listOf(
jar?.absolutePath
?: File("/jars/Velocity.jar").absolutePath
?: File("/jar/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(workingDir, "devtempworld")
var jarFile = jar?.absolutePath
?: File(serverDir, "devtempworld")
val 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 = gameModeTemplate?.worldTemplate ?: File(serverDir, "Bauwelt")
val templateFile = File(serverDir, "Bauwelt")
if (!templateFile.exists()) {
throw CliktError("Could not find world template: ${templateFile.absolutePath}")
throw CliktError("World Template not found!")
}
templateFile.copyRecursively(worldFile)
}
@@ -135,12 +123,6 @@ class DevCommand : CliktCommand("dev") {
}
}
data class GameModeTemplate(
val serverDir: File,
val worldTemplate: File,
val spigot: Boolean
)
val jvmDefaultParams = arrayOf(
"-Xmx1G",
"-Xgc:excessiveGCratio=80",
@@ -170,7 +152,8 @@ class DevCommand : CliktCommand("dev") {
)
val additionalVersions = mapOf(
"Lobby" to 21
"Tutorial" to 15,
"Lobby" to 20
)
fun findVersion(server: String): Int? =
@@ -183,41 +166,6 @@ 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<Map<String, Any?>>(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<String>()
?.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<String>) {
args += "-DlogPath=${workingDir.absolutePath}/logs"
args += "-Dlog4j.configurationFile=${log4jConfig.absolutePath}"
@@ -243,4 +191,4 @@ class DevCommand : CliktCommand("dev") {
Runtime.getRuntime().addShutdownHook(Thread { if (process.isAlive) process.destroyForcibly() })
process.waitFor()
}
}
}
@@ -32,10 +32,7 @@ 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.FightUI;
import de.steamwar.fightsystem.utils.HullHider;
import de.steamwar.fightsystem.utils.SWSound;
import de.steamwar.fightsystem.utils.TechHiderWrapper;
import de.steamwar.fightsystem.utils.*;
import de.steamwar.linkage.AbstractLinker;
import de.steamwar.linkage.SpigotLinker;
import de.steamwar.message.Message;
@@ -27,9 +27,12 @@ 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) {
@@ -44,6 +47,7 @@ public class EnternCountdown extends Countdown {
}
private final FightPlayer fightPlayer;
private List<ProtocolUtils.ChunkPos> chunkPos;
public EnternCountdown(FightPlayer fp, Countdown countdown) {
super(calcTime(fp, countdown), new Message("ENTERN_COUNTDOWN"), SWSound.BLOCK_NOTE_PLING, false);
@@ -24,7 +24,6 @@ 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;
@@ -89,7 +88,7 @@ public class Fight {
}
public static void playSound(Sound sound, float volume, float pitch) {
GlobalRecorder.getInstance().soundAtPlayer(Registry.SOUNDS.getKey(sound).getKey(), volume, pitch);
GlobalRecorder.getInstance().soundAtPlayer(sound.name(), volume, pitch);
//volume: max. 100, pitch: max. 2
Bukkit.getServer().getOnlinePlayers().forEach(player -> player.playSound(player, sound, volume, pitch));
}
@@ -35,9 +35,7 @@ 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.FightUI;
import de.steamwar.fightsystem.utils.ItemBuilder;
import de.steamwar.fightsystem.utils.Region;
import de.steamwar.fightsystem.utils.*;
import de.steamwar.fightsystem.winconditions.Wincondition;
import de.steamwar.fightsystem.winconditions.Winconditions;
import de.steamwar.inventory.SWItem;
@@ -24,7 +24,6 @@ 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;
@@ -39,13 +38,11 @@ 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);
}
}
@@ -25,6 +25,7 @@ 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;
@@ -20,6 +20,7 @@
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;
@@ -36,6 +37,7 @@ 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;
@@ -113,18 +115,18 @@ public class Recording implements Listener {
}.register();
new StateDependent(ArenaMode.AntiReplay, FightState.Ingame) {
private final BiFunction<Player, ServerboundUseItemPacket, Object> place = Recording.this::blockPlace;
private final BiFunction<Player, ServerboundPlayerActionPacket, Object> dig = Recording.this::blockDig;
private final BiFunction<Player, Object, Object> dig = Recording.this::blockDig;
@Override
public void enable() {
TinyProtocol.instance.addFilter(ServerboundUseItemPacket.class, place);
TinyProtocol.instance.addFilter(ServerboundPlayerActionPacket.class, dig);
TinyProtocol.instance.addFilter(blockDigPacket, dig);
}
@Override
public void disable() {
TinyProtocol.instance.removeFilter(ServerboundUseItemPacket.class, place);
TinyProtocol.instance.removeFilter(ServerboundPlayerActionPacket.class, dig);
TinyProtocol.instance.removeFilter(blockDigPacket, dig);
}
}.register();
new StateDependentTask(ArenaMode.AntiReplay, FightState.All, () -> {
@@ -141,8 +143,13 @@ public class Recording implements Listener {
GlobalRecorder.getInstance().entitySpeed(entity);
}
private Object blockDig(Player p, ServerboundPlayerActionPacket packet) {
if (!isNotSent(p) && packet.getAction() == ServerboundPlayerActionPacket.Action.RELEASE_USE_ITEM) {
private static final Class<? extends Packet<?>> blockDigPacket = ServerboundPlayerActionPacket.class;
private static final Class<?> playerDigType = blockDigPacket.getDeclaredClasses()[0];
private static final Reflection.Field<?> blockDigType = Reflection.getField(blockDigPacket, playerDigType, 0);
private static final Object releaseUseItem = playerDigType.getEnumConstants()[5];
private Object blockDig(Player p, Object packet) {
if (!isNotSent(p) && blockDigType.get(packet) == releaseUseItem) {
GlobalRecorder.getInstance().bowSpan(p, false, false);
}
return packet;
@@ -34,9 +34,7 @@ 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.FightUI;
import de.steamwar.fightsystem.utils.Message;
import de.steamwar.fightsystem.utils.TechHiderWrapper;
import de.steamwar.fightsystem.utils.*;
import de.steamwar.sql.SchematicNode;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.Team;
@@ -512,13 +510,11 @@ public class PacketProcessor implements Listener {
float volume = source.readFloat();
float pitch = source.readFloat();
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;
Sound sound = Sound.valueOf(soundName);
execSync(() -> {
Location location = new Location(Config.world, x, y, z);
location.getWorld().playSound(location, finalSound, SoundCategory.valueOf(soundCategory), volume, pitch);
location.getWorld().playSound(location, sound, SoundCategory.valueOf(soundCategory), volume, pitch);
});
}
@@ -528,11 +524,9 @@ public class PacketProcessor implements Listener {
float volume = source.readFloat();
float pitch = source.readFloat();
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;
Sound sound = Sound.valueOf(soundName);
execSync(() -> Fight.playSound(finalSound, volume, pitch));
execSync(() -> Fight.playSound(sound, volume, pitch));
}
private void pasteSchem(FightTeam team) throws IOException {
@@ -33,7 +33,6 @@ 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;
@@ -239,7 +238,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, Registry.SOUNDS.getKey(soundType.getSound()).getKey(), soundCategory, volume, pitch);
write(0x32, x, y, z, soundType.getSound().name(), soundCategory, volume, pitch);
}
default void soundAtPlayer(String soundType, float volume, float pitch) {
@@ -19,6 +19,7 @@
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;
@@ -50,7 +51,7 @@ import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@@ -81,13 +82,18 @@ 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<BlockEntityType<?>> blockEntityTypeToObfuscate = Config.GameModeConfig.Techhider.HiddenBlockEntities.stream()
.map(id -> {
.map((id) -> {
ResourceLocation loc = ResourceLocation.parse(id);
return BuiltInRegistries.BLOCK_ENTITY_TYPE.get(loc).orElse(null);
return ((Optional<Holder.Reference<BlockEntityType<?>>>) method.invoke(blockEntityType, loc)).get().value();
})
.filter(Objects::nonNull)
.map(Holder.Reference::value)
.collect(Collectors.toUnmodifiableSet());
new TechHider(CraftMagicNumbers.getBlock(Config.GameModeConfig.Techhider.ObfuscateWith), new AccessPrivilegeProvider() {
+30
View File
@@ -30,6 +30,16 @@ dependencies {
implementation(project(":FightSystem:FightSystem_Core"))
}
tasks.register<FightServer>("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<FightServer>("HalloweenWS") {
group = "run"
description = "Run a Halloween 1.21 Fight Replay Server"
@@ -53,3 +63,23 @@ tasks.register<FightServer>("WarGear21") {
config = "WarGear21.yml"
jar = "/jars/paper-1.21.6.jar"
}
tasks.register<FightServer>("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<FightServer>("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"
}
+7
View File
@@ -1,3 +1,5 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
/*
* This file is a part of the SteamWar software.
*
@@ -41,3 +43,8 @@ dependencies {
implementation(libs.mysql)
implementation("org.slf4j:slf4j-simple:2.0.17")
}
val compileKotlin: KotlinCompile by tasks
compileKotlin.compilerOptions {
freeCompilerArgs.set(listOf("-XXLanguage:+ContextParameters"))
}
@@ -0,0 +1,63 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package de.steamwar.kotlin.ui
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
class Observer<T>(private var value: T): ReadWriteProperty<Any?, T> {
private val listeners = mutableSetOf<RenderBoundary>()
fun removeListener(render: RenderBoundary) {
listeners.remove(render)
}
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
return value
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
this.value = value
notifyListeners()
}
fun set(value: T) {
this.value = value
notifyListeners()
}
fun get() = value
fun update(update: (T) -> T) {
this.value = update(value)
notifyListeners()
}
private fun notifyListeners() {
listeners.forEach { it.render() }
}
context(render: RenderBoundary)
fun listen(): Observer<T> {
listeners.add(render)
render.observers.add(this)
return this
}
}
@@ -17,14 +17,15 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.cannonCore
package de.steamwar.kotlin.ui
import org.bukkit.Location
abstract class RenderBoundary {
val observers = mutableSetOf<Observer<*>>()
class CannonCore(val location: Location) {
abstract fun render()
companion object Manager {
var activeCores: Observable<List<CannonCore>> = Observable(ArrayList())
open fun destroy() {
observers.forEach { it.removeListener(this) }
observers.clear()
}
}
}
@@ -17,13 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.cannonCore
package de.steamwar.kotlin.ui
import org.bukkit.plugin.Plugin
fun register(plugin: Plugin) {
CannonCoreCommand.register()
val pluginManager = plugin.server.pluginManager
pluginManager.registerEvents(CannonCoreWand, plugin)
}
@DslMarker
annotation class RenderMarker()
@@ -17,33 +17,37 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.cannonCore
package de.steamwar.kotlin.ui
import de.steamwar.core.SWPlayer
import de.steamwar.cursor.Cursor
import org.bukkit.Material
import de.steamwar.kotlin.ui.context.WindowContext
import org.bukkit.entity.Player
import org.bukkit.event.inventory.InventoryType
class EmptyCannonCoreWandDisplay : SWPlayer.Component {
val coresDisplay: CannonCoresDisplay
val cursor: Cursor
abstract class UIInventory(val player: Player): RenderBoundary() {
var window: UIWindow? = null
constructor(owner: Player) {
coresDisplay = CannonCoresDisplay(owner)
abstract fun view()
cursor = Cursor(
coresDisplay.displayServer, owner, Material.GLASS, Material.COMMAND_BLOCK,
listOf(
Cursor.CursorMode.BLOCK_ALIGNED
),
) { location, hitEntity, action ->
print("Hello")
fun open() {
if (window == null) {
view()
assert(window != null) { "View method must create a inventory" }
}
SWPlayer.of(owner).setComponent(this)
window!!.open()
}
override fun onUnmount(player: SWPlayer) {
player.removeComponent(cursor::class.java)
override fun render() {
window?.onClose()
window = null
open()
}
protected fun inventory(size: Int, title: String, init: WindowContext.() -> Unit) {
window = UIWindow(size, title, player, init)
}
protected fun inventory(type: InventoryType, title: String, init: WindowContext.() -> Unit) {
window = UIWindow(type, title, player, init)
}
}
@@ -0,0 +1,90 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package de.steamwar.kotlin.ui
import de.steamwar.kotlin.KotlinCore
import de.steamwar.kotlin.ui.context.WindowContext
import net.kyori.adventure.text.Component
import org.bukkit.Bukkit
import org.bukkit.entity.Player
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.inventory.ClickType
import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.event.inventory.InventoryCloseEvent
import org.bukkit.event.inventory.InventoryType
import org.bukkit.inventory.Inventory
import org.bukkit.inventory.InventoryHolder
import org.bukkit.inventory.InventoryView
class UIWindow(val player: Player, val render: WindowContext.() -> Unit): InventoryHolder {
val onClicks = mutableMapOf<Int, (event: InventoryClickEvent) -> Unit>()
private var updating = false
lateinit var bukkitInv: Inventory
private set
val context by lazy { WindowContext(this) }
constructor(size: Int, title: String, player: Player, render: WindowContext.() -> Unit): this(player, render) {
bukkitInv = KotlinCore.plugin.server.createInventory(this, size * 9, Component.translatable(title))
}
constructor(type: InventoryType, title: String, player: Player, render: WindowContext.() -> Unit): this(player, render) {
bukkitInv = KotlinCore.plugin.server.createInventory(this, type, Component.translatable(title));
}
fun open() {
render(context)
player.openInventory(bukkitInv)
}
fun onClose() {
if (updating) return
onClicks.clear()
context.destroy()
}
override fun getInventory() = bukkitInv
companion object : Listener {
init {
Bukkit.getPluginManager().registerEvents(this, KotlinCore.plugin)
}
@EventHandler
fun onInventoryClick(event: InventoryClickEvent) {
val window = event.inventory.holder
if (window is UIWindow) {
event.isCancelled = true
if (window.context.skipDoubleClick && event.click == ClickType.DOUBLE_CLICK) return
window.onClicks[event.slot]?.invoke(event)
}
}
@EventHandler
fun onInventoryClose(event: InventoryCloseEvent) {
val window = event.inventory.holder
if (window is UIWindow) {
window.onClose()
}
}
}
}
@@ -0,0 +1,56 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package de.steamwar.kotlin.ui.components
import de.steamwar.kotlin.ui.RenderBoundary
import de.steamwar.kotlin.ui.RenderMarker
import de.steamwar.kotlin.ui.context.GroupContext
import de.steamwar.kotlin.ui.context.RenderParent
import org.bukkit.Material
import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.inventory.ItemStack
@RenderMarker
class ItemContext(val parent: RenderParent, val renderFunc: ItemContext.() -> Unit): RenderBoundary() {
override fun render() {
val oldX = x
val oldY = y
renderFunc(this)
parent.renderItem(x, y, item, onClick)
if (oldX != x || oldY != y) {
parent.resetSlot(oldX, oldY)
}
}
init {
render()
}
var item: ItemStack = ItemStack.of(Material.AIR)
var onClick: (event: InventoryClickEvent) -> Unit = {}
var x: Int = 0
var y: Int = 0
fun onClick(func: (event: InventoryClickEvent) -> Unit) {
onClick = func
}
}
fun GroupContext.item(init: ItemContext.() -> Unit) = children.add(ItemContext(this, init))
@@ -0,0 +1,62 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package de.steamwar.kotlin.ui.context
import de.steamwar.kotlin.ui.RenderBoundary
import de.steamwar.kotlin.ui.RenderMarker
import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.inventory.ItemStack
@RenderMarker
open class GroupContext(val parent: RenderParent?, val init: GroupContext.() -> Unit): RenderBoundary(), RenderParent {
val children = mutableListOf<RenderBoundary>()
val updatedSlots = mutableSetOf<Pair<Int, Int>>()
override fun destroy() {
children.forEach { it.destroy() }
super.destroy()
}
init {
init(this)
}
override fun renderItem(x: Int, y: Int, item: ItemStack, onClick: (event: InventoryClickEvent) -> Unit) {
updatedSlots.add(x to y)
parent?.renderItem(x, y, item, onClick)
}
override fun resetSlot(x: Int, y: Int) {
parent?.resetSlot(x, y)
}
fun group(init: GroupContext.() -> Unit) {
children.add(GroupContext(this, init))
}
override fun render() {
children.forEach { it.destroy() }
children.clear()
val oldUpdatedSlots = updatedSlots.toList()
updatedSlots.clear()
init(this)
(oldUpdatedSlots - updatedSlots).forEach { resetSlot(it.first, it.second) }
}
}
@@ -17,19 +17,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.cannonCore
package de.steamwar.kotlin.ui.context
import de.steamwar.bausystem.SWUtils
import de.steamwar.command.SWCommand
import de.steamwar.linkage.Linked
import org.bukkit.entity.Player
import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.inventory.ItemStack
object CannonCoreCommand : SWCommand("cannoncore") {
interface RenderParent {
fun renderItem(x: Int, y: Int, item: ItemStack, onClick: (event: InventoryClickEvent) -> Unit)
@Register
fun giveCannonCoreWand(player: Player) {
val wandItem = CannonCoreWand.getWandItem()
SWUtils.giveItemToPlayer(player, wandItem)
}
fun resetSlot(x: Int, y: Int)
}
@@ -17,30 +17,29 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.cannonCore
package de.steamwar.kotlin.ui.context
import de.steamwar.entity.RBlockDisplay
import de.steamwar.entity.REntityAction
import de.steamwar.entity.REntityServer
import de.steamwar.entity.RInteraction
import org.bukkit.Location
import de.steamwar.kotlin.ui.RenderMarker
import de.steamwar.kotlin.ui.UIWindow
import org.bukkit.Material
import org.bukkit.entity.Player
import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.inventory.ItemStack
class CannonCoreEntity: RBlockDisplay {
val entityMaterial = Material.COMMAND_BLOCK
val hitbox: RInteraction
constructor(server: REntityServer, location: Location, onClick: (player: Player, clickAction: REntityAction) -> Unit) : super(server, location) {
setBlock(entityMaterial.createBlockData())
hitbox = RInteraction(server, location)
hitbox.setCallback(onClick)
@RenderMarker
class WindowContext(val window: UIWindow): GroupContext(null, {}) {
override fun renderItem(x: Int, y: Int, item: ItemStack, onClick: (event: InventoryClickEvent) -> Unit) {
window.bukkitInv.setItem(x + y * 9, item)
window.onClicks[x + y * 9] = onClick
}
override fun die() {
super.die()
hitbox.die()
override fun resetSlot(x: Int, y: Int) {
window.bukkitInv.setItem(x + y * 9, ItemStack.of(Material.AIR))
window.onClicks.remove(x + y * 9)
}
}
fun outsideClick(click: (event: InventoryClickEvent) -> Unit) {
window.onClicks[-999] = click
}
var skipDoubleClick = true
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
package de.steamwar.kotlin.util
import net.kyori.adventure.text.Component
import org.bukkit.Bukkit
import org.bukkit.Material
import org.bukkit.enchantments.Enchantment
import org.bukkit.inventory.ItemFlag
import org.bukkit.inventory.ItemStack
import org.bukkit.inventory.meta.SkullMeta
import org.bukkit.inventory.meta.components.CustomModelDataComponent
fun ItemStack.count(count: Int) = apply { amount = count }
fun ItemStack.name(name: Component) = apply { itemMeta = itemMeta.also { it.displayName(name) } }
fun ItemStack.lored(lore: List<Component>) = apply { itemMeta = itemMeta.also { it.lore(lore) } }
fun String.asMaterial(): Material = Material.matchMaterial(this) ?: Material.BARRIER
fun skull(owner: String): ItemStack = ItemStack(Material.PLAYER_HEAD)
.also {
it.editMeta(SkullMeta::class.java) {
it.playerProfile = Bukkit.getOfflinePlayer(owner.trimStart('.')).playerProfile.also { pp -> pp.complete() }
}
}
fun ItemStack.hideAttributes() = apply { itemMeta = itemMeta.also { it.addItemFlags(*ItemFlag.entries.toTypedArray()) } }
fun ItemStack.enchanted() = apply { itemMeta = itemMeta.also { it.addEnchant(Enchantment.UNBREAKING, 10, true) } }
fun ItemStack.customModelData(model: CustomModelDataComponent) = apply { itemMeta = itemMeta.also { it.setCustomModelDataComponent(model) } }
+3 -9
View File
@@ -19,7 +19,6 @@
plugins {
steamwar.java
widener
}
dependencies {
@@ -33,16 +32,11 @@ dependencies {
compileOnly(libs.fawe)
}
widener {
fromCatalog(libs.nms)
fromCatalog(libs.paperapi)
}
tasks.register<DevServer>("DevLobby") {
tasks.register<DevServer>("DevLobby20") {
group = "run"
description = "Run a Dev Lobby"
description = "Run a 1.20 Dev Lobby"
dependsOn(":SpigotCore:shadowJar")
dependsOn(":LobbySystem:jar")
template = "Lobby21"
template = "Lobby20"
worldName = "Lobby"
}
@@ -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] = matchColor(new Color(i));
colors[i] = MapPalette.matchColor(new Color(i));
}
} else {
try {
@@ -57,26 +57,4 @@ 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));
}
}
@@ -254,8 +254,7 @@ public class CustomMap implements Listener {
int green = pixels[i2];
int i3 = (y * width + x) * numBands + 2;
int blue = pixels[i3];
int colorIndex = ColorInit.getColorByte(red, green, blue);
Color nearest = MapPalette.colors[colorIndex >= 0 ? colorIndex : colorIndex + 256];
Color nearest = MapPalette.getColor(ColorInit.getColorByte(red, green, blue));
pixels[(y * width + x) * numBands] = nearest.getRed();
pixels[i2] = nearest.getGreen();
-6
View File
@@ -1,6 +0,0 @@
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;
@@ -20,12 +20,9 @@
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;
@@ -43,14 +40,14 @@ public class SlowMoRunner {
if (currentTime > 0) {
current += 1;
if (current % 5 == 0) {
tickRateManager.setFrozen(false);
SlowMoUtils.unfreeze();
current = 0;
} else {
tickRateManager.setFrozen(true);
SlowMoUtils.freeze();
}
currentTime--;
} else {
tickRateManager.setFrozen(false);
SlowMoUtils.unfreeze();
}
}, 0, 1);
}
@@ -0,0 +1,74 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
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;
}
}
}
}
@@ -37,8 +37,8 @@ public class AutoChecker {
public static final AutoChecker impl = new AutoChecker();
public AutoCheckerResult check(Clipboard clipboard, GameModeConfig<Material, String> type) {
return AutoCheckerResult.builder().type(type).height(clipboard.getDimensions().x()).width(clipboard.getDimensions().x())
.depth(clipboard.getDimensions().z()).blockScanResult(scan(clipboard, 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()))
@@ -46,19 +46,19 @@ public class AutoChecker {
}
public AutoCheckerResult sizeCheck(Clipboard clipboard, GameModeConfig<Material, String> type) {
return AutoCheckerResult.builder().type(type).height(clipboard.getDimensions().y()).width(clipboard.getDimensions().x())
.depth(clipboard.getDimensions().z()).build();
return AutoCheckerResult.builder().type(type).height(clipboard.getDimensions().getBlockY()).width(clipboard.getDimensions().getBlockX())
.depth(clipboard.getDimensions().getBlockZ()).build();
}
public AutoChecker.BlockScanResult scan(Clipboard clipboard, GameModeConfig<Material, String> type) {
AutoChecker.BlockScanResult result = new AutoChecker.BlockScanResult();
BlockVector3 min = clipboard.getMinimumPoint();
BlockVector3 max = clipboard.getMaximumPoint();
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++) {
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++) {
final BaseBlock block = clipboard.getFullBlock(BlockVector3.at(x, y, z));
final Material material = Material.matchMaterial(block.getBlockType().id());
final Material material = Material.matchMaterial(block.getBlockType().getId());
if (material == null) {
continue;
}
@@ -69,7 +69,7 @@ public class AutoChecker {
checkInventory(result, block, material, new BlockPos(x, y, z), type);
}
if (x == min.x() || x == max.x() || y == max.y() || z == min.z() || z == max.z()) {
if (x == min.getBlockX() || x == max.getBlockX() || y == max.getBlockY() || z == min.getBlockZ() || z == max.getBlockZ()) {
result.getDesignBlocks().computeIfAbsent(material, m -> new ArrayList<>()).add(new BlockPos(x, y, z));
}
}
@@ -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.x(); i <= max.x(); i++) {
for (int j = min.y(); j <= max.y(); j++) {
for (int k = min.z(); k <= max.z(); k++) {
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++) {
BlockVector3 vector = BlockVector3.at(i, j, k);
BaseBlock block = clipboard.getFullBlock(vector);
if (toReplace.contains(Material.matchMaterial(block.getBlockType().id()))) {
if (toReplace.contains(Material.matchMaterial(block.getBlockType().getId()))) {
clipboard.setBlock(vector, replaceType.toBaseBlock());
}
}
+1 -4
View File
@@ -19,7 +19,6 @@
plugins {
steamwar.java
widener
}
tasks.compileJava {
@@ -59,8 +58,6 @@ dependencies {
compileOnly(libs.netty)
compileOnly(libs.brigadier)
compileOnly(libs.fastutil)
}
widener {
fromCatalog(libs.nms)
implementation(libs.anvilgui)
}
@@ -1,6 +1,7 @@
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.*;
@@ -179,12 +180,22 @@ 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();
serverChannels.add(serverChannel);
serverChannel.pipeline().addFirst(serverChannelHandler);
// Find the correct list, or implicitly throw an exception
boolean looking = true;
for (int i = 0; looking; i++) {
List<Object> 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;
}
}
}
@@ -0,0 +1,315 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
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<String, String> 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<T> {
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 <T> Field<T> getField(Class<?> target, String name, Class<T> fieldType) {
return getField(target, name, fieldType, 0);
}
public static <T> Field<T> getField(Class<?> target, Class<T> fieldType, int index) {
return getField(target, null, fieldType, index);
}
public static <T> Field<T> getField(Class<?> target, Class<T> fieldType, int index, Class<?>... parameters) {
return getField(target, null, fieldType, index, parameters);
}
private static <T> Field<T> getField(Class<?> target, String name, Class<T> 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 <T> boolean matching(java.lang.reflect.Field field, String name, Class<T> 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);
}
}
}
@@ -22,20 +22,35 @@ 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 SimpleCommandMap commandMap;
private static final CommandMap commandMap;
private static final Map<String, Command> knownCommandMap;
static {
commandMap = ((CraftServer) Bukkit.getServer()).getCommandMap();
knownCommandMap = commandMap.getKnownCommands();
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<String, Command>) knownCommandsField.get(commandMap);
} catch (NoSuchFieldException | IllegalAccessException exception) {
Bukkit.shutdown();
throw new SecurityException("Oh shit. Commands cannot be registered.", exception);
}
}
static void unregister(Command command) {
@@ -19,11 +19,20 @@
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();
@@ -35,4 +44,60 @@ 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<Object>) Reflection.getField(dataWatcherRegistry, dataWatcherSerializer, 0, type).get(null));
}
public Object getDataWatcherItem(Object dwo, Object value) {
return new SynchedEntityData.DataItem<>((EntityDataAccessor<Object>) dwo, value);
}
public BountifulWrapper.PositionSetter getPositionSetter(Class<?> packetClass, int fieldOffset) {
try {
Reflection.Field<PositionMoveRotation> 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<Double> posX = Reflection.getField(packetClass, double.class, fieldOffset);
Reflection.Field<Double> posY = Reflection.getField(packetClass, double.class, fieldOffset + 1);
Reflection.Field<Double> 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<UUID> 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);
}
}
@@ -20,6 +20,7 @@
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;
@@ -95,6 +96,9 @@ class CheckpointUtilsJ9 {
}
}
private static final Reflection.Field<List> channelFutures = Reflection.getField(ServerConnectionListener.class, List.class, 0, ChannelFuture.class);
private static void freezeInternal(Path path) throws Exception {
Bukkit.getPluginManager().callEvent(new CRIUSleepEvent());
@@ -105,9 +109,9 @@ class CheckpointUtilsJ9 {
// Close socket
ServerConnectionListener serverConnection = MinecraftServer.getServer().getConnection();
List<ChannelFuture> channels = serverConnection.channels;
for (ChannelFuture future : channels) {
future.channel().close().syncUninterruptibly();
List<?> channels = channelFutures.get(serverConnection);
for (Object future : channels) {
((ChannelFuture) future).channel().close().syncUninterruptibly();
}
channels.clear();
@@ -141,8 +145,8 @@ class CheckpointUtilsJ9 {
// Reopen socket
serverConnection.startTcpServerListener(InetAddress.getLoopbackAddress(), port);
for (ChannelFuture future : channels) {
future.channel().config().setAutoRead(true);
for (Object future : channels) {
((ChannelFuture) future).channel().config().setAutoRead(true);
}
Bukkit.getPluginManager().callEvent(new CRIUWakeupEvent());
@@ -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 versionStrings 3 of the License, or
* (at your option) any later versionStrings.
* 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
@@ -20,6 +20,7 @@
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;
@@ -48,13 +49,9 @@ public class Core extends JavaPlugin {
public static final Message MESSAGE = new Message("SpigotCore", Core.class.getClassLoader());
@Getter
@Deprecated
private static final int version;
static {
String[] versionStrings = Bukkit.getServer().getBukkitVersion().split("-")[0].split("\\.");
version = Integer.parseInt(versionStrings[1]);
public static int getVersion() {
return Reflection.MAJOR_VERSION;
}
@Getter
@@ -19,6 +19,7 @@
package de.steamwar.core;
import de.steamwar.Reflection;
import de.steamwar.sql.SWException;
import org.spigotmc.WatchdogThread;
@@ -38,7 +39,9 @@ public class ErrorHandler extends Handler {
public ErrorHandler() {
Logger.getLogger("").addHandler(this);
watchdogThreadId = WatchdogThread.instance.threadId();
Reflection.Field<WatchdogThread> getInstance = Reflection.getField(WatchdogThread.class, WatchdogThread.class, 0);
watchdogThreadId = getInstance.get(null).getId();
}
void unregister() {
@@ -0,0 +1,34 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package de.steamwar.core;
import lombok.experimental.UtilityClass;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.network.chat.contents.PlainTextContents;
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();
}
}
@@ -20,6 +20,7 @@
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;
@@ -32,8 +33,13 @@ public class WorldIdentifier {
private static ResourceKey<Level> 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 = new ResourceKey<>(new ResourceLocation("minecraft", "dimension"), new ResourceLocation("steamwar", name));
resourceKey = (ResourceKey<Level>) resourceKeyConstructor.invoke(minecraftKeyConstructor.invoke("minecraft", "dimension"), minecraftKeyConstructor.invoke("steamwar", name));
}
public WorldIdentifier() {
@@ -22,6 +22,7 @@ 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;
@@ -34,10 +35,13 @@ public class SteamwarGameProfileRepository implements GameProfileRepository {
public static final SteamwarGameProfileRepository impl = new SteamwarGameProfileRepository();
private static final GameProfileRepository fallback;
private static final Reflection.Field<Services> field;
private static final Services current;
static {
current = MinecraftServer.getServer().services;
Class<?> clazz = MinecraftServer.getServer().getClass();
field = Reflection.getField(clazz, Services.class, 0);
current = field.get(MinecraftServer.getServer());
fallback = current.profileRepository();
}
@@ -64,6 +68,7 @@ public class SteamwarGameProfileRepository implements GameProfileRepository {
}
public void inject() {
MinecraftServer.getServer().services = new Services(current.sessionService(), current.servicesKeySet(), this, current.profileCache(), current.paperConfigurations());
Services newServices = new Services(current.sessionService(), current.servicesKeySet(), this, current.profileCache(), current.paperConfigurations());
field.set(MinecraftServer.getServer(), newServices);
}
}
@@ -20,13 +20,16 @@
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;
@@ -35,6 +38,7 @@ 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 {
@@ -13,7 +13,7 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerInteractEvent;
import java.util.Optional;
import java.util.*;
@Linked
@@ -49,7 +49,7 @@ public class RArmorStand extends REntity implements RInteractableEntity<RArmorSt
super.spawn(packetSink);
if (size != null && size != Size.NORMAL) {
entityDataPacket().add(sizeWatcher, size.value).send(packetSink);
packetSink.accept(getDataWatcherPacket(sizeWatcher, size.value));
}
}
@@ -61,9 +61,9 @@ public class RBlockDisplay extends RDisplay {
private static final EntityDataAccessor<BlockState> blockWatcher = new EntityDataAccessor<>(23, EntityDataSerializers.BLOCK_STATE);
private void getBlock(boolean ignoreDefault, EntityDataPacketBuilder packetSink) {
private void getBlock(boolean ignoreDefault, BiConsumer<Object, Object> packetSink) {
if (ignoreDefault || !block.getAsString(true).equals(DEFAULT_BLOCK.getAsString(true))) {
packetSink.add(blockWatcher, ((CraftBlockData) block).getState());
packetSink.accept(blockWatcher, ((CraftBlockData) block).getState());
}
}
}
@@ -89,13 +89,18 @@ public abstract class RDisplay extends REntity {
}
@SafeVarargs
protected final void sendPacket(Consumer<Object> packetSink, BiConsumer<Boolean, EntityDataPacketBuilder>... dataSinkSinks) {
EntityDataPacketBuilder builder = entityDataPacket();
protected final void sendPacket(Consumer<Object> packetSink, BiConsumer<Boolean, BiConsumer<Object, Object>>... dataSinkSinks) {
List<Object> keyValueData = new ArrayList<>();
boolean ignoreDefault = packetSink == updatePacketSink;
for (BiConsumer<Boolean, EntityDataPacketBuilder> dataSinkSink : dataSinkSinks) {
dataSinkSink.accept(ignoreDefault, builder);
for (BiConsumer<Boolean, BiConsumer<Object, Object>> dataSinkSink : dataSinkSinks) {
dataSinkSink.accept(ignoreDefault, (dataWatcher, value) -> {
keyValueData.add(dataWatcher);
keyValueData.add(value);
});
}
if (!keyValueData.isEmpty()) {
packetSink.accept(getDataWatcherPacket(keyValueData.toArray()));
}
if (!builder.isEmpty()) builder.send(packetSink);
}
public void setTransform(@NonNull Transformation transform) {
@@ -108,12 +113,12 @@ public abstract class RDisplay extends REntity {
private static final EntityDataAccessor<Vector3f> scaleWatcher = new EntityDataAccessor<>(12, EntityDataSerializers.VECTOR3);
private static final EntityDataAccessor<Quaternionf> rightRotationWatcher = new EntityDataAccessor<>(14, EntityDataSerializers.QUATERNION);
private void getTransformData(boolean ignoreDefault, EntityDataPacketBuilder dataSink) {
private void getTransformData(boolean ignoreDefault, BiConsumer<Object, Object> dataSink) {
if (ignoreDefault || !transform.equals(DEFAULT_TRANSFORM)) {
dataSink.add(translationWatcher, transform.getTranslation());
dataSink.add(leftRotationWatcher, transform.getLeftRotation());
dataSink.add(scaleWatcher, transform.getScale());
dataSink.add(rightRotationWatcher, transform.getRightRotation());
dataSink.accept(translationWatcher, transform.getTranslation());
dataSink.accept(leftRotationWatcher, transform.getLeftRotation());
dataSink.accept(scaleWatcher, transform.getScale());
dataSink.accept(rightRotationWatcher, transform.getRightRotation());
}
}
@@ -125,10 +130,10 @@ public abstract class RDisplay extends REntity {
private static final EntityDataAccessor<Integer> transformationInterpolationDurationWatcher = new EntityDataAccessor<>(9, EntityDataSerializers.INT);
private static final EntityDataAccessor<Integer> positionOrRotationInterpolationDurationWatcher = new EntityDataAccessor<>(10, EntityDataSerializers.INT);
private void getInterpolationDuration(boolean ignoreDefault, EntityDataPacketBuilder packetSink) {
private void getInterpolationDuration(boolean ignoreDefault, BiConsumer<Object, Object> packetSink) {
if (ignoreDefault || interpolationDelay != 0) {
packetSink.add(transformationInterpolationDurationWatcher, interpolationDuration);
packetSink.add(positionOrRotationInterpolationDurationWatcher, interpolationDuration);
packetSink.accept(transformationInterpolationDurationWatcher, interpolationDuration);
packetSink.accept(positionOrRotationInterpolationDurationWatcher, interpolationDuration);
}
}
@@ -139,9 +144,9 @@ public abstract class RDisplay extends REntity {
private static final EntityDataAccessor<Float> viewRangeWatcher = new EntityDataAccessor<>(17, EntityDataSerializers.FLOAT);
private void getViewRange(boolean ignoreDefault, EntityDataPacketBuilder packetSink) {
private void getViewRange(boolean ignoreDefault, BiConsumer<Object, Object> packetSink) {
if (ignoreDefault || viewRange != 1.0F) {
packetSink.add(viewRangeWatcher, viewRange);
packetSink.accept(viewRangeWatcher, viewRange);
}
}
@@ -152,9 +157,9 @@ public abstract class RDisplay extends REntity {
private static final EntityDataAccessor<Float> shadowRadiusWatcher = new EntityDataAccessor<>(18, EntityDataSerializers.FLOAT);
private void getShadowRadius(boolean ignoreDefault, EntityDataPacketBuilder packetSink) {
private void getShadowRadius(boolean ignoreDefault, BiConsumer<Object, Object> packetSink) {
if (ignoreDefault || shadowRadius != 0.0F) {
packetSink.add(shadowRadiusWatcher, shadowRadius);
packetSink.accept(shadowRadiusWatcher, shadowRadius);
}
}
@@ -165,9 +170,9 @@ public abstract class RDisplay extends REntity {
private static final EntityDataAccessor<Float> shadowStrengthWatcher = new EntityDataAccessor<>(19, EntityDataSerializers.FLOAT);
private void getShadowStrength(boolean ignoreDefault, EntityDataPacketBuilder packetSink) {
private void getShadowStrength(boolean ignoreDefault, BiConsumer<Object, Object> packetSink) {
if (ignoreDefault || shadowStrength != 1.0F) {
packetSink.add(shadowStrengthWatcher, shadowStrength);
packetSink.accept(shadowStrengthWatcher, shadowStrength);
}
}
@@ -178,9 +183,9 @@ public abstract class RDisplay extends REntity {
private static final EntityDataAccessor<Float> displayWidthWatcher = new EntityDataAccessor<>(20, EntityDataSerializers.FLOAT);
private void getDisplayWidth(boolean ignoreDefault, EntityDataPacketBuilder packetSink) {
private void getDisplayWidth(boolean ignoreDefault, BiConsumer<Object, Object> packetSink) {
if (ignoreDefault || displayWidth != 0.0F) {
packetSink.add(displayWidthWatcher, displayWidth);
packetSink.accept(displayWidthWatcher, displayWidth);
}
}
@@ -191,9 +196,9 @@ public abstract class RDisplay extends REntity {
private static final EntityDataAccessor<Float> displayHeightWatcher = new EntityDataAccessor<>(21, EntityDataSerializers.FLOAT);
private void getDisplayHeight(boolean ignoreDefault, EntityDataPacketBuilder packetSink) {
private void getDisplayHeight(boolean ignoreDefault, BiConsumer<Object, Object> packetSink) {
if (ignoreDefault || displayHeight != 0.0F) {
packetSink.add(displayHeightWatcher, displayHeight);
packetSink.accept(displayHeightWatcher, displayHeight);
}
}
@@ -204,9 +209,9 @@ public abstract class RDisplay extends REntity {
private static final EntityDataAccessor<Integer> interpolationDelayWatcher = new EntityDataAccessor<>(8, EntityDataSerializers.INT);
private void getInterpolationDelay(boolean ignoreDefault, EntityDataPacketBuilder packetSink) {
private void getInterpolationDelay(boolean ignoreDefault, BiConsumer<Object, Object> packetSink) {
if (ignoreDefault || interpolationDelay != 0) {
packetSink.add(interpolationDelayWatcher, interpolationDelay);
packetSink.accept(interpolationDelayWatcher, interpolationDelay);
}
}
@@ -217,9 +222,9 @@ public abstract class RDisplay extends REntity {
private static final EntityDataAccessor<Byte> billboardWatcher = new EntityDataAccessor<>(15, EntityDataSerializers.BYTE);
private void getBillboard(boolean ignoreDefault, EntityDataPacketBuilder packetSink) {
private void getBillboard(boolean ignoreDefault, BiConsumer<Object, Object> packetSink) {
if (ignoreDefault || billboard != Display.Billboard.FIXED) {
packetSink.add(billboardWatcher, (byte) billboard.ordinal());
packetSink.accept(billboardWatcher, (byte) billboard.ordinal());
}
}
@@ -230,9 +235,9 @@ public abstract class RDisplay extends REntity {
private static final EntityDataAccessor<Integer> glowColorOverrideWatcher = new EntityDataAccessor<>(22, EntityDataSerializers.INT);
private void getGlowColorOverride(boolean ignoreDefault, EntityDataPacketBuilder packetSink) {
private void getGlowColorOverride(boolean ignoreDefault, BiConsumer<Object, Object> packetSink) {
if (ignoreDefault || glowColorOverride != null) {
packetSink.add(glowColorOverrideWatcher, glowColorOverride == null ? -1 : glowColorOverride.asARGB());
packetSink.accept(glowColorOverrideWatcher, glowColorOverride == null ? -1 : glowColorOverride.asARGB());
}
}
@@ -243,9 +248,9 @@ public abstract class RDisplay extends REntity {
private static final EntityDataAccessor<Integer> brightnessWatcher = new EntityDataAccessor<>(16, EntityDataSerializers.INT);
private void getBrightness(boolean ignoreDefault, EntityDataPacketBuilder packetSink) {
private void getBrightness(boolean ignoreDefault, BiConsumer<Object, Object> packetSink) {
if (ignoreDefault || brightness != null) {
packetSink.add(brightnessWatcher, brightness == null ? -1 : brightness.getBlockLight() << 4 | brightness.getSkyLight() << 20);
packetSink.accept(brightnessWatcher, brightness == null ? -1 : brightness.getBlockLight() << 4 | brightness.getSkyLight() << 20);
}
}
}
@@ -20,12 +20,13 @@
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;
@@ -43,6 +44,7 @@ import org.bukkit.inventory.ItemStack;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
public class REntity {
@@ -172,10 +174,14 @@ public class REntity {
server.postEntityMove(this, fromX, fromZ);
}
private static final Class<?> animationPacket = ClientboundAnimatePacket.class;
private static final Reflection.Field<Integer> animationEntity = Reflection.getField(animationPacket, int.class, 5);
private static final Reflection.Field<Integer> animationAnimation = Reflection.getField(animationPacket, int.class, 6);
public void showAnimation(byte animation) {
ClientboundAnimatePacket packet = new ClientboundAnimatePacket();
packet.id = entityId;
packet.action = animation;
Object packet = Reflection.newInstance(animationPacket);
animationEntity.set(packet, entityId);
animationAnimation.set(packet, (int) animation);
server.updateEntity(this, packet);
}
@@ -183,21 +189,25 @@ 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<Integer> statusEntity = Reflection.getField(statusPacket, int.class, 0);
private static final Reflection.Field<Byte> statusStatus = Reflection.getField(statusPacket, byte.class, 0);
public void showDamage() {
ClientboundEntityEventPacket packet = new ClientboundEntityEventPacket();
packet.entityId = entityId;
packet.eventId = (byte) 2;
Object packet = Reflection.newInstance(statusPacket);
statusEntity.set(packet, entityId);
statusStatus.set(packet, (byte) 2);
server.updateEntity(this, packet);
}
public void setPose(Pose pose) {
this.pose = pose;
server.updateEntity(this, entityDataPacket().add(poseDataWatcher, pose).build());
server.updateEntity(this, getDataWatcherPacket(poseDataWatcher, pose));
}
public void setOnFire(boolean perma) {
fireTick = perma ? -1 : 21;
server.updateEntity(this, entityDataPacket().add(entityStatusWatcher, getEntityStatus()).build());
server.updateEntity(this, getDataWatcherPacket(entityStatusWatcher, getEntityStatus()));
}
public boolean isOnFire() {
@@ -206,18 +216,20 @@ public class REntity {
public void setInvisible(boolean invisible) {
this.invisible = invisible;
server.updateEntity(this, entityDataPacket().add(entityStatusWatcher, getEntityStatus()).build());
server.updateEntity(this, getDataWatcherPacket(entityStatusWatcher, getEntityStatus()));
}
public void setBowDrawn(boolean drawn, boolean offHand) {
bowDrawn = drawn;
server.updateEntity(this, entityDataPacket().add(poseDataWatcher, Pose.SHOOTING).build());
server.updateEntity(this, getDataWatcherPacket(poseDataWatcher, Pose.SHOOTING));
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
server.updateEntity(this, entityDataPacket().add(nameWatcher, formatDisplayName(displayName))
.add(nameVisibleWatcher, displayName != null).build());
server.updateEntity(this, getDataWatcherPacket(
nameWatcher, FlatteningWrapper.formatDisplayName(displayName),
nameVisibleWatcher, displayName != null
));
}
public void setItem(Object slot, ItemStack stack) {
@@ -231,18 +243,22 @@ public class REntity {
public void setNoGravity(boolean noGravity) {
this.noGravity = noGravity;
server.updateEntity(this, entityDataPacket().add(noGravityDataWatcher, noGravity).build());
server.updateEntity(this, getDataWatcherPacket(noGravityDataWatcher, noGravity));
}
public void setGlowing(boolean glowing) {
this.isGlowing = glowing;
server.updateEntity(this, entityDataPacket().add(entityStatusWatcher, getEntityStatus()).build());
server.updateEntity(this, getDataWatcherPacket(entityStatusWatcher, getEntityStatus()));
}
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);
private static final Function<REntity, Object> spawnPacketGenerator = entitySpawnPacketGenerator(ClientboundAddEntityPacket.class, 2);
private static final Reflection.Field<Integer> additionalData = Reflection.getField(ClientboundAddEntityPacket.class, int.class, 4);
private Object spawnPacketGenerator() {
Object packet = spawnPacketGenerator.apply(this);
additionalData.set(packet, objectData);
return packet;
}
void list(Consumer<Object> packetSink) {
@@ -260,22 +276,20 @@ public class REntity {
}
if (pose != Pose.STANDING) {
entityDataPacket().add(poseDataWatcher, pose).send(packetSink);
packetSink.accept(getDataWatcherPacket(poseDataWatcher, pose));
}
byte status = getEntityStatus();
if (status != 0) {
entityDataPacket().add(entityStatusWatcher, getEntityStatus()).send(packetSink);
packetSink.accept(getDataWatcherPacket(entityStatusWatcher, getEntityStatus()));
}
if (displayName != null) {
entityDataPacket().add(nameWatcher, formatDisplayName(displayName))
.add(nameVisibleWatcher, true)
.send(packetSink);
packetSink.accept(getDataWatcherPacket(nameWatcher, FlatteningWrapper.formatDisplayName(displayName), nameVisibleWatcher, true));
}
if (noGravity) {
entityDataPacket().add(noGravityDataWatcher, true).send(packetSink);
packetSink.accept(getDataWatcherPacket(noGravityDataWatcher, true));
}
}
@@ -283,7 +297,7 @@ public class REntity {
if (fireTick > 0) {
fireTick--;
if (fireTick == 0) {
server.updateEntity(this, entityDataPacket().add(entityStatusWatcher, getEntityStatus()).build());
server.updateEntity(this, getDataWatcherPacket(entityStatusWatcher, getEntityStatus()));
}
}
}
@@ -314,29 +328,13 @@ public class REntity {
return status;
}
protected EntityDataPacketBuilder entityDataPacket() {
return new EntityDataPacketBuilder();
}
public class EntityDataPacketBuilder {
private List<SynchedEntityData.DataValue<?>> values = new ArrayList<>();
public <T> EntityDataPacketBuilder add(EntityDataAccessor<T> accessor, T value) {
values.add(new SynchedEntityData.DataItem<>(accessor, value).value());
return this;
protected Object getDataWatcherPacket(Object... dataWatcherKeyValues) {
ArrayList<SynchedEntityData.DataValue<?>> 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());
}
public boolean isEmpty() {
return values.isEmpty();
}
public ClientboundSetEntityDataPacket build() {
return new ClientboundSetEntityDataPacket(entityId, values);
}
public void send(Consumer<Object> packetSink) {
packetSink.accept(build());
}
return new ClientboundSetEntityDataPacket(entityId, nativeWatchers);
}
private Object getTeleportPacket() {
@@ -361,10 +359,14 @@ public class REntity {
}
}
private static final Class<?> headRotationPacket = ClientboundRotateHeadPacket.class;
private static final Reflection.Field<Integer> headRotationEntity = Reflection.getField(headRotationPacket, int.class, 0);
private static final Reflection.Field<Byte> headRotationYaw = Reflection.getField(headRotationPacket, byte.class, 0);
private Object getHeadRotationPacket() {
ClientboundRotateHeadPacket packet = new ClientboundRotateHeadPacket();
packet.entityId = entityId;
packet.yHeadRot = headYaw;
Object packet = Reflection.newInstance(headRotationPacket);
headRotationEntity.set(packet, entityId);
headRotationYaw.set(packet, headYaw);
return packet;
}
@@ -372,6 +374,33 @@ public class REntity {
return new ClientboundSetEquipmentPacket(entityId, Collections.singletonList(Pair.of((EquipmentSlot) slot, CraftItemStack.asNMSCopy(stack))));
}
private static final Reflection.Field<net.minecraft.world.entity.EntityType> spawnType = Reflection.getField(ClientboundAddEntityPacket.class, net.minecraft.world.entity.EntityType.class, 0);
private static Function<REntity, Object> entitySpawnPacketGenerator(Class<?> spawnPacket, int posOffset) {
BountifulWrapper.UUIDSetter uuid = BountifulWrapper.impl.getUUIDSetter(spawnPacket);
Function<REntity, Object> 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<REntity, Object> spawnPacketGenerator(Class<?> spawnPacket, int posOffset) {
Reflection.Field<Integer> 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));
}
@@ -379,8 +408,4 @@ public class REntity {
private int calcVelocity(double value) {
return (int) (Math.max(-3.9, Math.min(value, 3.9)) * 8000);
}
private static Optional<Component> formatDisplayName(String displayName) {
return displayName != null ? Optional.of(MutableComponent.create(PlainTextContents.create(displayName))) : Optional.empty();
}
}
@@ -20,6 +20,7 @@
package de.steamwar.entity;
import de.steamwar.techhider.BlockIds;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import org.bukkit.Location;
@@ -19,13 +19,14 @@
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;
@@ -60,13 +61,18 @@ public class RInteraction extends REntity implements RInteractableEntity<RIntera
}
@SafeVarargs
protected final void sendPacket(Consumer<Object> packetSink, BiConsumer<Boolean, EntityDataPacketBuilder>... dataSinkSinks) {
EntityDataPacketBuilder builder = entityDataPacket();
protected final void sendPacket(Consumer<Object> packetSink, BiConsumer<Boolean, BiConsumer<Object, Object>>... dataSinkSinks) {
List<Object> keyValueData = new ArrayList<>();
boolean ignoreDefault = packetSink == updatePacketSink;
for (BiConsumer<Boolean, EntityDataPacketBuilder> dataSinkSink : dataSinkSinks) {
dataSinkSink.accept(ignoreDefault, builder);
for (BiConsumer<Boolean, BiConsumer<Object, Object>> dataSinkSink : dataSinkSinks) {
dataSinkSink.accept(ignoreDefault, (dataWatcher, value) -> {
keyValueData.add(dataWatcher);
keyValueData.add(value);
});
}
if (!keyValueData.isEmpty()) {
packetSink.accept(getDataWatcherPacket(keyValueData.toArray()));
}
if (!builder.isEmpty()) builder.send(packetSink);
}
public void setInteractionWidth(float interactionWidth) {
@@ -74,11 +80,11 @@ public class RInteraction extends REntity implements RInteractableEntity<RIntera
sendPacket(updatePacketSink, this::getInteractionWidthData);
}
private static final EntityDataAccessor<Float> interactionWidthWatcher = new EntityDataAccessor<>(8, EntityDataSerializers.FLOAT);
private static final Object interactionWidthWatcher = BountifulWrapper.impl.getDataWatcherObject(8, Float.class);
private void getInteractionWidthData(boolean ignoreDefault, EntityDataPacketBuilder dataSink) {
private void getInteractionWidthData(boolean ignoreDefault, BiConsumer<Object, Object> dataSink) {
if (ignoreDefault || interactionWidth != 1.0) {
dataSink.add(interactionWidthWatcher, interactionWidth);
dataSink.accept(interactionWidthWatcher, interactionWidth);
}
}
@@ -87,11 +93,11 @@ public class RInteraction extends REntity implements RInteractableEntity<RIntera
sendPacket(updatePacketSink, this::getInteractionHeightData);
}
private static final EntityDataAccessor<Float> interactionHeightWatcher = new EntityDataAccessor<>(9, EntityDataSerializers.FLOAT);
private static final Object interactionHeightWatcher = BountifulWrapper.impl.getDataWatcherObject(9, Float.class);
private void getInteractionHeightData(boolean ignoreDefault, EntityDataPacketBuilder dataSink) {
private void getInteractionHeightData(boolean ignoreDefault, BiConsumer<Object, Object> dataSink) {
if (ignoreDefault || interactionHeight != 1.0) {
dataSink.add(interactionHeightWatcher, interactionHeight);
dataSink.accept(interactionHeightWatcher, interactionHeight);
}
}
@@ -100,11 +106,11 @@ public class RInteraction extends REntity implements RInteractableEntity<RIntera
sendPacket(updatePacketSink, this::getResponsiveData);
}
private static final EntityDataAccessor<Boolean> responsiveWatcher = new EntityDataAccessor<>(10, EntityDataSerializers.BOOLEAN);
private static final Object responsiveWatcher = BountifulWrapper.impl.getDataWatcherObject(10, Boolean.class);
private void getResponsiveData(boolean ignoreDefault, EntityDataPacketBuilder dataSink) {
private void getResponsiveData(boolean ignoreDefault, BiConsumer<Object, Object> dataSink) {
if (ignoreDefault || !responsive) {
dataSink.add(responsiveWatcher, responsive);
dataSink.accept(responsiveWatcher, responsive);
}
}
}
@@ -63,9 +63,9 @@ public class RItemDisplay extends RDisplay {
private static final EntityDataAccessor<net.minecraft.world.item.ItemStack> itemStackWatcher = new EntityDataAccessor<>(23, EntityDataSerializers.ITEM_STACK);
private void getItemStack(boolean ignoreDefault, EntityDataPacketBuilder packetSink) {
private void getItemStack(boolean ignoreDefault, BiConsumer<Object, Object> packetSink) {
if (ignoreDefault || !itemStack.equals(DEFAULT_ITEM_STACK)) {
packetSink.add(itemStackWatcher, CraftItemStack.asNMSCopy(itemStack));
packetSink.accept(itemStackWatcher, CraftItemStack.asNMSCopy(itemStack));
}
}
@@ -76,9 +76,9 @@ public class RItemDisplay extends RDisplay {
sendPacket(updatePacketSink, this::getItemDisplayTransform);
}
private void getItemDisplayTransform(boolean ignoreDefault, EntityDataPacketBuilder packetSink) {
private void getItemDisplayTransform(boolean ignoreDefault, BiConsumer<Object, Object> packetSink) {
if (ignoreDefault || itemDisplayTransform != ItemDisplay.ItemDisplayTransform.NONE) {
packetSink.add(itemDisplayTransformWatcher, (byte) itemDisplayTransform.ordinal());
packetSink.accept(itemDisplayTransformWatcher, (byte) itemDisplayTransform.ordinal());
}
}
}
@@ -21,6 +21,7 @@ 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;
@@ -28,8 +29,6 @@ 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;
@@ -43,7 +42,7 @@ import java.util.function.Consumer;
public class RPlayer extends REntity implements RInteractableEntity<RPlayer> {
private static final EntityDataAccessor<Byte> skinPartsDataWatcher = new EntityDataAccessor<>(17, EntityDataSerializers.BYTE);
private static final Object skinPartsDataWatcher = BountifulWrapper.impl.getDataWatcherObject(17, Byte.class);
@Getter
private final UUID actualUUID;
@@ -86,7 +85,7 @@ public class RPlayer extends REntity implements RInteractableEntity<RPlayer> {
@Override
void spawn(Consumer<Object> packetSink) {
packetSink.accept(getNamedSpawnPacket());
entityDataPacket().add(skinPartsDataWatcher, (byte) 0x7F).send(packetSink);
packetSink.accept(getDataWatcherPacket(skinPartsDataWatcher, (byte) 0x7F));
for (Map.Entry<Object, ItemStack> entry : itemSlots.entrySet()) {
packetSink.accept(getEquipmentPacket(entry.getKey(), entry.getValue()));
@@ -77,9 +77,9 @@ public class RTextDisplay extends RDisplay {
private static final EntityDataAccessor<Component> textWatcher = new EntityDataAccessor<>(23, EntityDataSerializers.COMPONENT);
private void getText(boolean ignoreDefault, EntityDataPacketBuilder packetSink) {
private void getText(boolean ignoreDefault, BiConsumer<Object, Object> packetSink) {
if (ignoreDefault || !text.isEmpty()) {
packetSink.add(textWatcher, MutableComponent.create(PlainTextContents.create(text)));
packetSink.accept(textWatcher, MutableComponent.create(PlainTextContents.create(text)));
}
}
@@ -90,9 +90,9 @@ public class RTextDisplay extends RDisplay {
private static final EntityDataAccessor<Integer> lineWidthWatcher = new EntityDataAccessor<>(24, EntityDataSerializers.INT);
private void getLineWidth(boolean ignoreDefault, EntityDataPacketBuilder packetSink) {
private void getLineWidth(boolean ignoreDefault, BiConsumer<Object, Object> packetSink) {
if (ignoreDefault || lineWidth != 200) {
packetSink.add(lineWidthWatcher, lineWidth);
packetSink.accept(lineWidthWatcher, lineWidth);
}
}
@@ -103,9 +103,9 @@ public class RTextDisplay extends RDisplay {
private static final EntityDataAccessor<Byte> textOpacityWatcher = new EntityDataAccessor<>(26, EntityDataSerializers.BYTE);
private void getTextOpacity(boolean ignoreDefault, EntityDataPacketBuilder packetSink) {
private void getTextOpacity(boolean ignoreDefault, BiConsumer<Object, Object> packetSink) {
if (ignoreDefault || textOpacity != (byte) -1) {
packetSink.add(textOpacityWatcher, textOpacity);
packetSink.accept(textOpacityWatcher, textOpacity);
}
}
@@ -126,9 +126,9 @@ public class RTextDisplay extends RDisplay {
private static final EntityDataAccessor<Integer> backgroundColorWatcher = new EntityDataAccessor<>(25, EntityDataSerializers.INT);
private void getBackgroundColor(boolean ignoreDefault, EntityDataPacketBuilder packetSink) {
private void getBackgroundColor(boolean ignoreDefault, BiConsumer<Object, Object> packetSink) {
if (ignoreDefault || backgroundColor != null) {
packetSink.add(backgroundColorWatcher, backgroundColor);
packetSink.accept(backgroundColorWatcher, backgroundColor);
}
}
@@ -146,7 +146,7 @@ public class RTextDisplay extends RDisplay {
private static final EntityDataAccessor<Byte> textStatusWatcher = new EntityDataAccessor<>(27, EntityDataSerializers.BYTE);
private void getTextStatus(boolean ignoreDefault, EntityDataPacketBuilder packetSink) {
private void getTextStatus(boolean ignoreDefault, BiConsumer<Object, Object> packetSink) {
byte status = 0;
if (shadowed) {
@@ -167,7 +167,7 @@ public class RTextDisplay extends RDisplay {
}
if (ignoreDefault || status != 0) {
packetSink.add(textStatusWatcher, status);
packetSink.accept(textStatusWatcher, status);
}
}
}
@@ -19,8 +19,10 @@
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;
@@ -33,8 +35,13 @@ import java.util.List;
import java.util.function.UnaryOperator;
public class ChunkHider {
private static final UnaryOperator<ClientboundLevelChunkWithLightPacket> chunkPacketShallowCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkWithLightPacket.class, ClientboundLevelChunkWithLightPacket::new);
private static final UnaryOperator<ClientboundLevelChunkPacketData> chunkDataShallowCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkPacketData.class, ClientboundLevelChunkPacketData::new);
private static final UnaryOperator<Object> chunkPacketShallowCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkWithLightPacket.class);
private static final UnaryOperator<Object> chunkDataShallowCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkPacketData.class);
private static final Reflection.Field<ClientboundLevelChunkPacketData> levelChunkPacketDataField = Reflection.getField(ClientboundLevelChunkWithLightPacket.class, ClientboundLevelChunkPacketData.class, 0);
private static final Reflection.Field<byte[]> chunkBlockDataField = Reflection.getField(ClientboundLevelChunkPacketData.class, byte[].class, 0);
private static final Reflection.Field<List> chunkBlockEntitiesDataField = Reflection.getField(ClientboundLevelChunkPacketData.class, List.class, 0);
private final int SECTION_SPAN_SIZE = 16;
private final byte BIT_PER_BLOCK_INDIRECTION_LIMIT = 8;
@@ -171,8 +178,8 @@ public class ChunkHider {
byte[] data = new byte[out.readableBytes()];
out.readBytes(data);
List<ClientboundLevelChunkPacketData.BlockEntityInfo> blockEntities = chunkData.blockEntitiesData;
List<ClientboundLevelChunkPacketData.BlockEntityInfo> filteredBlockEntities = filterBlockEntities(player, blockEntities, chunkX, chunkZ);
List<Object> blockEntities = chunkBlockEntitiesDataField.get(chunkData);
List<Object> filteredBlockEntities = filterBlockEntities(player, blockEntities, chunkX, chunkZ);
return buildNewChunkPacket(packet, data, filteredBlockEntities);
@@ -230,24 +237,33 @@ public class ChunkHider {
return reEncodedData.getRaw();
}
private ClientboundLevelChunkWithLightPacket buildNewChunkPacket(ClientboundLevelChunkWithLightPacket originalPacket, byte[] newBlockDataBuffer, List<ClientboundLevelChunkPacketData.BlockEntityInfo> newBlockEntities) {
ClientboundLevelChunkWithLightPacket clonedPacket = chunkPacketShallowCloner.apply(originalPacket);
ClientboundLevelChunkPacketData clonedPacketChunkData = chunkDataShallowCloner.apply(originalPacket.getChunkData());
private ClientboundLevelChunkWithLightPacket buildNewChunkPacket(ClientboundLevelChunkWithLightPacket originalPacket, byte[] newBlockDataBuffer, List<Object> newBlockEntities) {
ClientboundLevelChunkWithLightPacket clonedPacket = (ClientboundLevelChunkWithLightPacket) chunkPacketShallowCloner.apply(originalPacket);
ClientboundLevelChunkPacketData clonedPacketChunkData = (ClientboundLevelChunkPacketData) chunkDataShallowCloner.apply(originalPacket.getChunkData());
clonedPacketChunkData.buffer = newBlockDataBuffer;
clonedPacketChunkData.blockEntitiesData = newBlockEntities;
clonedPacket.chunkData = clonedPacketChunkData;
chunkBlockDataField.set(clonedPacketChunkData, newBlockDataBuffer);
chunkBlockEntitiesDataField.set(clonedPacketChunkData, newBlockEntities);
levelChunkPacketDataField.set(clonedPacket, clonedPacketChunkData);
return clonedPacket;
}
private List<ClientboundLevelChunkPacketData.BlockEntityInfo> filterBlockEntities(Player player, List<ClientboundLevelChunkPacketData.BlockEntityInfo> blockEntities, int chunkX, int chunkZ) {
private static final Class<?> blockEntitiyInfoClass = Reflection.getClass("net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData$BlockEntityInfo");
private static final Reflection.Field<BlockEntityType> blockEntityInfoTypeField = Reflection.getField(blockEntitiyInfoClass, BlockEntityType.class, 0);
private static final Reflection.Field<Integer> packedXZField = Reflection.getField(blockEntitiyInfoClass, int.class, 0);
private static final Reflection.Field<Integer> yField = Reflection.getField(blockEntitiyInfoClass, int.class, 1);
private List<Object> filterBlockEntities(Player player, List<Object> blockEntities, int chunkX, int chunkZ) {
int fourBitBitmask = 0b0000_1111;
return blockEntities.stream()
.filter((blockEntityInfo) -> {
BlockEntityType<?> type = blockEntityInfo.type;
int packedXZ = blockEntityInfo.packedXZ;
BlockEntityType<?> type = blockEntityInfoTypeField.get(blockEntityInfo);
int packedXZ = packedXZField.get(blockEntityInfo);
int localX = (packedXZ >> 4) & fourBitBitmask;
int localZ = packedXZ & fourBitBitmask;
@@ -255,7 +271,7 @@ public class ChunkHider {
int worldX = (chunkX * SECTION_SPAN_SIZE) + localX;
int worldZ = (chunkZ * SECTION_SPAN_SIZE) + localZ;
int worldY = blockEntityInfo.y;
int worldY = yField.get(blockEntityInfo);
return accessPrivilegeProvider.isPlayerPrivilegedToAccessPosition(player, worldX, worldY, worldZ) && accessPrivilegeProvider.isPlayerPrivilegedToAccessBlockEntity(player, worldX, worldY, worldZ, type);
}).toList();
@@ -19,37 +19,64 @@
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.Supplier;
import java.util.function.BiFunction;
import java.util.function.UnaryOperator;
public class ProtocolUtils {
private ProtocolUtils() {
}
public static <T> UnaryOperator<T> shallowCloneGenerator(Class<T> clazz, Supplier<T> supplier) {
BiConsumer<T, T> filler = shallowFill(clazz);
@Deprecated
public static BiFunction<Object, UnaryOperator<Object>, 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<Object> shallowCloneGenerator(Class<?> clazz) {
BiConsumer<Object, Object> filler = shallowFill(clazz);
return source -> {
T clone = supplier.get();
Object clone = Reflection.newInstance(clazz);
filler.accept(source, clone);
return clone;
};
}
private static <T> BiConsumer<T, T> shallowFill(Class<T> clazz) {
public static <T> UnaryOperator<T> shallowTypedCloneGenerator(Class<T> clazz) {
BiConsumer<Object, Object> filler = shallowFill(clazz);
return source -> {
Object clone = Reflection.newInstance(clazz);
filler.accept(source, clone);
return (T) clone;
};
}
private static BiConsumer<Object, Object> shallowFill(Class<?> clazz) {
if (clazz == null) {
return (source, clone) -> {
};
}
BiConsumer superFiller = shallowFill(clazz.getSuperclass());
BiConsumer<Object, Object> superFiller = shallowFill(clazz.getSuperclass());
Field[] fds = clazz.getDeclaredFields();
List<Field> fields = new ArrayList<>();
@@ -79,6 +106,25 @@ 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;
@@ -121,4 +167,53 @@ 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<Byte> 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;
}
}
}
@@ -20,6 +20,7 @@
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;
@@ -363,16 +364,24 @@ public class TechHider {
};
}
private final Reflection.Field<Integer> moveEntityPacketEntityIdField = Reflection.getField(ClientboundMoveEntityPacket.class, int.class, 0);
private Packet<?> processMoveEntityPacket(Player player, ClientboundMoveEntityPacket packet) {
if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, packet.entityId)) {
int entityId = moveEntityPacketEntityIdField.get(packet);
if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, entityId)) {
return packet;
} else {
return null;
}
}
private final Reflection.Field<Integer> rotateHeadPacketEntityIdField = Reflection.getField(ClientboundRotateHeadPacket.class, int.class, 0);
private Packet<?> processRotateHeadPacket(Player player, ClientboundRotateHeadPacket packet) {
if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, packet.entityId)) {
int entityId = rotateHeadPacketEntityIdField.get(packet);
if (privilegeProvider.isPlayerPrivilegedToAccessEntity(player, entityId)) {
return packet;
} else {
return null;
@@ -454,10 +463,14 @@ public class TechHider {
}
}
private final Reflection.Field<SectionPos> sectionPosField = Reflection.getField(ClientboundSectionBlocksUpdatePacket.class, SectionPos.class, 0);
private final Reflection.Field<short[]> oldPosField = Reflection.getField(ClientboundSectionBlocksUpdatePacket.class, short[].class, 0);
private final Reflection.Field<BlockState[]> oldStatesField = Reflection.getField(ClientboundSectionBlocksUpdatePacket.class, BlockState[].class, 0);
private ClientboundSectionBlocksUpdatePacket processSectionUpdate(Player p, ClientboundSectionBlocksUpdatePacket packet) {
SectionPos sectionPos = packet.sectionPos;
short[] oldPos = packet.positions;
BlockState[] oldStates = packet.states;
SectionPos sectionPos = sectionPosField.get(packet);
short[] oldPos = oldPosField.get(packet);
BlockState[] oldStates = oldStatesField.get(packet);
List<Short> filteredPos = new ArrayList<>(oldPos.length);
List<BlockState> filteredStates = new ArrayList<>(oldStates.length);
@@ -19,18 +19,22 @@
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;
@@ -40,26 +44,35 @@ import java.util.stream.Collectors;
public class ChunkHider {
public static final ChunkHider impl = new ChunkHider();
private static final UnaryOperator<ClientboundLevelChunkWithLightPacket> chunkPacketCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkWithLightPacket.class, ClientboundLevelChunkWithLightPacket::new);
private static final UnaryOperator<ClientboundLevelChunkPacketData> chunkDataCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkPacketData.class, ClientboundLevelChunkPacketData::new);
public Class<?> mapChunkPacket() {
return ClientboundLevelChunkWithLightPacket.class;
}
public BiFunction<Player, ClientboundLevelChunkWithLightPacket, ClientboundLevelChunkWithLightPacket> chunkHiderGenerator(TechHider techHider) {
private static final UnaryOperator<Object> chunkPacketCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkWithLightPacket.class);
private static final UnaryOperator<Object> chunkDataCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkPacketData.class);
private static final Reflection.Field<Integer> chunkXField = Reflection.getField(ClientboundLevelChunkWithLightPacket.class, int.class, 0);
private static final Reflection.Field<Integer> chunkZField = Reflection.getField(ClientboundLevelChunkWithLightPacket.class, int.class, 1);
private static final Reflection.Field<ClientboundLevelChunkPacketData> chunkData = Reflection.getField(ClientboundLevelChunkWithLightPacket.class, ClientboundLevelChunkPacketData.class, 0);
private static final Reflection.Field<byte[]> dataField = Reflection.getField(ClientboundLevelChunkPacketData.class, byte[].class, 0);
private static final Reflection.Field<List> tileEntities = Reflection.getField(ClientboundLevelChunkPacketData.class, List.class, 0);
public BiFunction<Player, Object, Object> chunkHiderGenerator(TechHider techHider) {
return (p, packet) -> {
int chunkX = packet.getX();
int chunkZ = packet.getZ();
int chunkX = chunkXField.get(packet);
int chunkZ = chunkZField.get(packet);
if (techHider.getLocationEvaluator().skipChunk(p, chunkX, chunkZ)) {
return packet;
}
packet = chunkPacketCloner.apply(packet);
ClientboundLevelChunkPacketData dataWrapper = chunkDataCloner.apply(packet.getChunkData());
Object dataWrapper = chunkDataCloner.apply(chunkData.get(packet));
Set<String> hiddenBlockEntities = techHider.getHiddenBlockEntities();
dataWrapper.blockEntitiesData = dataWrapper.blockEntitiesData.stream()
.filter(te -> tileEntityVisible(hiddenBlockEntities, te))
.collect(Collectors.toList());
tileEntities.set(dataWrapper, ((List<?>) tileEntities.get(dataWrapper)).stream().filter(te -> tileEntityVisible(hiddenBlockEntities, te)).collect(Collectors.toList()));
ByteBuf in = Unpooled.wrappedBuffer(dataWrapper.buffer);
ByteBuf in = Unpooled.wrappedBuffer(dataField.get(dataWrapper));
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);
@@ -75,16 +88,21 @@ public class ChunkHider {
byte[] data = new byte[out.readableBytes()];
out.readBytes(data);
dataWrapper.buffer = data;
dataField.set(dataWrapper, data);
packet.chunkData = dataWrapper;
chunkData.set(packet, dataWrapper);
return packet;
};
}
protected boolean tileEntityVisible(Set<String> hiddenBlockEntities, ClientboundLevelChunkPacketData.BlockEntityInfo tile) {
BlockEntityType<?> type = tile.type;
String path = BuiltInRegistries.BLOCK_ENTITY_TYPE.getKey(type).getPath();
private static final Registry<BlockEntityType<?>> 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<BlockEntityType> entityType = Reflection.getField(tileEntity, BlockEntityType.class, 0);
protected boolean tileEntityVisible(Set<String> hiddenBlockEntities, Object tile) {
BlockEntityType type = entityType.get(tile);
String path = ((ResourceLocation) getKey.invoke(registry, type)).getPath();
return !hiddenBlockEntities.contains(path);
}
@@ -19,8 +19,10 @@
package de.steamwar.techhider.legacy;
import de.steamwar.Reflection;
import net.minecraft.core.SectionPos;
import net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.entity.SignBlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import org.bukkit.entity.Player;
@@ -31,20 +33,24 @@ import java.util.function.BiFunction;
public class ProtocolWrapper {
public static final ProtocolWrapper impl = new ProtocolWrapper();
public BiFunction<Player, ClientboundSectionBlocksUpdatePacket, ClientboundSectionBlocksUpdatePacket> multiBlockChangeGenerator(TechHider techHider) {
private static final Reflection.Field<SectionPos> multiBlockChangeChunk = Reflection.getField(TechHider.multiBlockChangePacket, SectionPos.class, 0);
private static final Reflection.Field<short[]> multiBlockChangePos = Reflection.getField(TechHider.multiBlockChangePacket, short[].class, 0);
private static final Reflection.Field<BlockState[]> multiBlockChangeBlocks = Reflection.getField(TechHider.multiBlockChangePacket, BlockState[].class, 0);
public BiFunction<Player, Object, Object> multiBlockChangeGenerator(TechHider techHider) {
return (p, packet) -> {
TechHider.LocationEvaluator locationEvaluator = techHider.getLocationEvaluator();
SectionPos chunkCoords = packet.sectionPos;
int chunkX = chunkCoords.getX();
int chunkY = chunkCoords.getY();
int chunkZ = chunkCoords.getZ();
Object chunkCoords = multiBlockChangeChunk.get(packet);
int chunkX = TechHider.blockPositionX.get(chunkCoords);
int chunkY = TechHider.blockPositionY.get(chunkCoords);
int chunkZ = TechHider.blockPositionZ.get(chunkCoords);
if (locationEvaluator.skipChunkSection(p, chunkX, chunkY, chunkZ)) {
return packet;
}
packet = TechHider.multiBlockChangeCloner.apply(packet);
final short[] oldPos = packet.positions;
final BlockState[] oldBlocks = packet.states;
final short[] oldPos = multiBlockChangePos.get(packet);
final BlockState[] oldBlocks = multiBlockChangeBlocks.get(packet);
ArrayList<Short> poss = new ArrayList<>(oldPos.length);
ArrayList<BlockState> blocks = new ArrayList<>(oldPos.length);
for (int i = 0; i < oldPos.length; i++) {
@@ -71,9 +77,16 @@ public class ProtocolWrapper {
newPos[i] = poss.get(i);
}
packet.positions = newPos;
packet.states = blocks.toArray(BlockState[]::new);
multiBlockChangePos.set(packet, newPos);
multiBlockChangeBlocks.set(packet, blocks.toArray(new BlockState[0]));
return packet;
};
}
private static final Reflection.Field<BlockEntityType> 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;
}
}
@@ -20,6 +20,7 @@
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;
@@ -27,7 +28,6 @@ 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,6 +37,7 @@ 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;
@@ -44,6 +45,10 @@ 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<Integer> blockPositionX = Reflection.getField(baseBlockPosition, int.class, 0);
public static final Reflection.Field<Integer> blockPositionY = Reflection.getField(baseBlockPosition, int.class, 1);
public static final Reflection.Field<Integer> blockPositionZ = Reflection.getField(baseBlockPosition, int.class, 2);
public static final Class<?> iBlockData = BlockState.class;
public static final Class<?> block = Block.class;
@@ -74,11 +79,11 @@ public class TechHider {
this.obfuscationTarget = CraftMagicNumbers.getBlock(obfuscationTarget).defaultBlockState();
this.obfuscationTargetId = BlockIds.impl.materialToId(obfuscationTarget);
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(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(ServerboundUseItemOnPacket.class, (p, packet) -> locationEvaluator.suppressInteractions(p) ? null : packet);
techhiders.put(ServerboundInteractPacket.class, (p, packet) -> locationEvaluator.suppressInteractions(p) ? null : packet);
@@ -92,43 +97,53 @@ public class TechHider {
techhiders.forEach(TinyProtocol.instance::removeFilter);
}
public static final UnaryOperator<ClientboundSectionBlocksUpdatePacket> multiBlockChangeCloner = ProtocolUtils.shallowCloneGenerator(ClientboundSectionBlocksUpdatePacket.class, ClientboundSectionBlocksUpdatePacket::new);
public static final Class<?> multiBlockChangePacket = ClientboundSectionBlocksUpdatePacket.class;
public static final UnaryOperator<Object> multiBlockChangeCloner = ProtocolUtils.shallowCloneGenerator(TechHider.multiBlockChangePacket);
private static final UnaryOperator<ClientboundBlockUpdatePacket> blockChangeCloner = ProtocolUtils.shallowCloneGenerator(ClientboundBlockUpdatePacket.class, ClientboundBlockUpdatePacket::new);
private static final Class<?> blockChangePacket = ClientboundBlockUpdatePacket.class;
private static final Function<Object, Object> 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 ClientboundBlockUpdatePacket blockChangeHider(Player p, ClientboundBlockUpdatePacket packet) {
switch (locationEvaluator.checkBlockPos(p, packet.getPos())) {
private Object blockChangeHider(Player p, Object packet) {
switch (locationEvaluator.checkBlockPos(p, blockChangePosition.get(packet))) {
case SKIP:
return packet;
case CHECK:
if (!iBlockDataHidden(packet.blockState)) {
if (!iBlockDataHidden((BlockState) blockChangeBlockData.get(packet))) {
return packet;
}
case HIDE:
packet = blockChangeCloner.apply(packet);
packet.blockState = (BlockState) obfuscationTarget;
blockChangeBlockData.set(packet, obfuscationTarget);
return packet;
case HIDE_AIR:
default:
packet = blockChangeCloner.apply(packet);
packet.blockState = (BlockState) AIR;
blockChangeBlockData.set(packet, AIR);
return packet;
}
}
private Object blockActionHider(Player p, ClientboundBlockEventPacket packet) {
if (locationEvaluator.checkBlockPos(p, packet.getPos()) == State.SKIP) {
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) {
return packet;
}
return null;
}
private ClientboundBlockEntityDataPacket tileEntityDataHider(Player p, ClientboundBlockEntityDataPacket packet) {
switch (locationEvaluator.checkBlockPos(p, packet.getPos())) {
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))) {
case SKIP:
return packet;
case CHECK:
if (packet.getType() != BlockEntityType.SIGN) {
if (ProtocolWrapper.impl.unfilteredTileEntityDataAction(packet)) {
return packet;
}
default:
@@ -158,8 +173,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, Vec3i pos) {
return check(player, pos.getX(), pos.getY(), pos.getZ());
default State checkBlockPos(Player player, Object pos) {
return check(player, blockPositionX.get(pos), blockPositionY.get(pos), blockPositionZ.get(pos));
}
default boolean blockPrecise(Player player, int x, int y, int z) {
@@ -1,890 +0,0 @@
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<Integer> interactableSlots;
/** An {@link Consumer} that is called when the anvil GUI is close */
private final Consumer<StateSnapshot> 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<Integer> interactableSlots,
Consumer<StateSnapshot> 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<List<ResponseAction>> actionsFuture =
clickHandler.apply(rawSlot, StateSnapshot.fromAnvilGUI(AnvilGUI.this));
final Consumer<List<ResponseAction>> 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<StateSnapshot> 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<Integer> 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<Integer> 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<StateSnapshot> closeListener) {
this.closeListener = closeListener;
return this;
}
/**
* Do an action when a slot is clicked in the inventory
* <p>
* 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.
* <p>
* 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<Integer, StateSnapshot, List<ResponseAction>> 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.
* <br><br>
* 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.
* <br>
* 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.
* <br>
* 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<Integer, StateSnapshot, CompletableFuture<List<ResponseAction>>> {}
/** 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<AnvilGUI, Player> {
/**
* 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 <code>null</code>
*/
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<ResponseAction> 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<ResponseAction> 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<ResponseAction> 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() : "";
}
}
}
@@ -1,151 +0,0 @@
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
* <br><br>
* 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);
}
}
@@ -1,71 +0,0 @@
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 <init> (Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/resources/ResourceLocation;)V
accessible method net/minecraft/resources/ResourceLocation <init> (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;
@@ -63,7 +63,7 @@ public abstract class Node {
nodes.forEach(consumer);
}
public abstract ProcessBuilder startServer(ServerVersion version, File directory, String worldDir, String levelName, int port, String... dParams);
public abstract ProcessBuilder startServer(String serverJar, File directory, String worldDir, String levelName, int port, String... dParams);
protected abstract ProcessBuilder prepareExecution(String... command);
@@ -97,8 +97,7 @@ public abstract class Node {
return hostname;
}
protected void constructServerstart(File directory, List<String> cmd, ServerVersion version, String worldDir, String levelName, int port, String... dParams) {
String serverJar = version.getServerJar();
protected void constructServerstart(File directory, List<String> cmd, String serverJar, String worldDir, String levelName, int port, String... dParams) {
if (JAVA_8.contains(serverJar)) {
cmd.add("/usr/lib/jvm/openj9-8/bin/java");
} else {
@@ -108,10 +107,6 @@ 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);
@@ -150,9 +145,9 @@ public abstract class Node {
}
@Override
public ProcessBuilder startServer(ServerVersion version, File directory, String worldDir, String levelName, int port, String... dParams) {
public ProcessBuilder startServer(String serverJar, File directory, String worldDir, String levelName, int port, String... dParams) {
List<String> cmd = new ArrayList<>();
constructServerstart(directory, cmd, version, worldDir, levelName, port, dParams);
constructServerstart(directory, cmd, serverJar, worldDir, levelName, port, dParams);
ProcessBuilder builder = new ProcessBuilder(cmd);
builder.directory(directory);
return builder;
@@ -182,7 +177,7 @@ public abstract class Node {
}
@Override
public ProcessBuilder startServer(ServerVersion version, File directory, String worldDir, String levelName, int port, String... dParams) {
public ProcessBuilder startServer(String serverJar, File directory, String worldDir, String levelName, int port, String... dParams) {
List<String> cmd = new ArrayList<>();
cmd.add("ssh");
cmd.add("-L");
@@ -191,7 +186,7 @@ public abstract class Node {
cmd.add("cd");
cmd.add(directory.getPath());
cmd.add(";");
constructServerstart(directory, cmd, version, worldDir, levelName, port, dParams);
constructServerstart(directory, cmd, serverJar, worldDir, levelName, port, dParams);
return new ProcessBuilder(cmd);
}
@@ -325,7 +325,7 @@ public class ServerStarter {
private void regularStart(String serverName, int port) {
postStart(constructor.construct(serverName, port, node.startServer(
version, directory, worldDir, worldName, port, arguments.entrySet().stream().map(entry -> entry.getKey() + "=" + entry.getValue()).toArray(String[]::new)
version.getServerJar(), directory, worldDir, worldName, port, arguments.entrySet().stream().map(entry -> entry.getKey() + "=" + entry.getValue()).toArray(String[]::new)
), worldCleanup, null));
}
@@ -23,7 +23,6 @@ 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;
@@ -34,7 +33,6 @@ 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),
@@ -49,7 +47,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, true);
PAPER_21("paper-1.21.6.jar", 21, ProtocolVersion.MINECRAFT_1_21_6);
private static final Map<String, ServerVersion> chatMap = new HashMap<>();
@@ -106,10 +104,6 @@ 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 + "/";
@@ -22,6 +22,7 @@ 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;
@@ -52,12 +53,12 @@ import net.dv8tion.jda.api.utils.MemberCachePolicy;
import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
import java.awt.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.*;
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";
@@ -1,7 +1,7 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
* 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
@@ -18,22 +18,21 @@
*/
plugins {
`kotlin-dsl`
`groovy-gradle-plugin`
}
repositories {
mavenCentral()
gradlePluginPortal()
id 'groovy-gradle-plugin'
}
sourceSets {
main {
groovy.srcDirs("src/main/groovy")
kotlin.srcDirs("src/main/kotlin")
groovy {
srcDirs("src/")
}
}
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.21")
}
}

Some files were not shown because too many files have changed in this diff Show More