Compare commits

..

8 Commits

Author SHA1 Message Date
Chaoscaot 1cb2e70ede Revert "Remove deprecated world management commands and associated SQL logic"
Pull Request Build / Build (pull_request) Failing after 58s
This reverts commit a7adfe37

Signed-off-by: Chaoscaot <max@maxsp.de>
2026-05-31 20:21:30 +02:00
Chaoscaot 8ce25ec19f Merge branch 'main' into world-system
Pull Request Build / Build (pull_request) Successful in 1m9s
2026-05-31 20:16:17 +02:00
Chaoscaot a7adfe378f Remove deprecated world management commands and associated SQL logic
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-05-20 16:44:32 +02:00
Chaoscaot 9aa363de6f Add world-based permissions and migration commands
Pull Request Build / Build (pull_request) Successful in 1m7s
- Switch bau logic from owner-scoped to world-scoped data
- Add CLI commands for world migration, templating, and archiving
- Extend world storage with team worlds and lock state support
2026-05-19 20:53:32 +02:00
Chaoscaot 5a5090c74d Remove unused import and fix incorrect import path in BuilderCloudCommand and World classes.
Pull Request Build / Build (pull_request) Successful in 1m8s
Signed-off-by: Chaoscaot <max@maxsp.de>
2026-05-19 19:36:43 +02:00
Chaoscaot 99ca7205fb Merge branch 'main' into world-system
# Conflicts:
#	VelocityCore/src/de/steamwar/velocitycore/ServerStarter.java
#	VelocityCore/src/de/steamwar/velocitycore/commands/BauCommand.java
#	VelocityCore/src/de/steamwar/velocitycore/commands/BuilderCloudCommand.java
2026-05-19 19:30:30 +02:00
Chaoscaot e85604fdc4 Merge branch 'main' into world-system
Pull Request Build / Build (pull_request) Failing after 42s
2026-05-16 14:49:18 +02:00
Chaoscaot 936ee38503 Migrate builder and bau worlds to shared world records
SteamWarCI Build successful
- add SQL-backed world entities with archive and rename support
- route builder, bau, deploy, and GDPR flows through world storage
- keep legacy folder support for existing worlds
2026-05-09 16:38:13 +02:00
129 changed files with 2969 additions and 3391 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);
}
}
@@ -19,7 +19,6 @@
plugins {
steamwar.java
widener
}
tasks.compileJava {
@@ -48,7 +47,3 @@ dependencies {
implementation(libs.luaj)
implementation(files("$projectDir/../libs/YAPION-SNAPSHOT.jar"))
}
widener {
fromCatalog(libs.nms)
}
@@ -834,10 +834,6 @@ SKIN_NO_REGION = §7You are not in a region with a changealbe skin
SKIN_ALREADY_EXISTS = §cThis skin already exists like this
SKIN_MESSAGE = §7Skin created
SKIN_MESSAGE_HOVER = §eClick to copy for YoyoNow and send
# Blast Resistance
BLASTRESISTANCE_HELP = §8/§eblastresistance §8-§7 Calculate min/max and average blast resistance of current clipboard
BLASTRESISTANCE_NO_CLIPBOARD = §cYou currently do not have a clipboard to be used.
BLASTRESISTANCE_RESULT = §7BlastResistance §8>>§7 Min§8: §e{0}§7 Max§8: §e{1}§7 Avg§8: §e{2}
# Panzern
PANZERN_HELP = §8/§epanzern §8[§7Block§8] §8[§7Slab§8] §8- §7Armor your WorldEdit selection
PANZERN_PREPARE1 = §71. Check, if barrels reach until border of armor.
@@ -772,10 +772,6 @@ SKIN_NO_REGION = §7Du steht in keiner Region, welche mit einem Skin versehen we
SKIN_ALREADY_EXISTS = §cDieser Skin existiert in der Form bereits
SKIN_MESSAGE = §7Skin erstellt
SKIN_MESSAGE_HOVER = §eKlicken zum kopieren für YoyoNow und an diesen senden
# Blast Resistance
BLASTRESISTANCE_HELP = §8/§eblastresistance §8-§7 Minimal-, Maximal- und durchschnittliche Sprengfestigkeit des aktuellen Inhalts der Zwischenablage berechnen
BLASTRESISTANCE_NO_CLIPBOARD = §cDerzeit steht Ihnen keine Zwischenablage zur Verfügung.
BLASTRESISTANCE_RESULT = §7BlastResistance §8>>§7 Min§8: §e{0}§7 Max§8: §e{1}§7 Avg§8: §e{2}
# Panzern
PANZERN_HELP = §8/§epanzern §8[§7Block§8] §8[§7Slab§8] §8- §7Panzer deine WorldEdit Auswahl
PANZERN_PREPARE1 = §71. Gucke nochmal nach, ob Läufe auch bis zur Panzergrenze führen.
@@ -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
@@ -133,7 +133,12 @@ public class BauSystem extends JavaPlugin implements Listener {
Bukkit.getWorlds().get(0).setGameRule(GameRule.SEND_COMMAND_FEEDBACK, false);
String identifier = BauServerInfo.getOwnerUser().getUUID().toString().replace("-", "");
String identifier;
if (BauServerInfo.getWorldId() != null) {
identifier = BauServerInfo.getWorldId().toString().replace("-", "");
} else {
identifier = BauServerInfo.getOwnerUser().getUUID().toString().replace("-", "");
}
WorldIdentifier.set("bau/" + Core.getVersion() + "/" + identifier);
}
@@ -224,4 +229,4 @@ public class BauSystem extends JavaPlugin implements Listener {
AtomicReference<BukkitTask> task = new AtomicReference<>();
task.set(runTaskTimer(plugin, () -> consumer.accept(task.get()), delay, period));
}
}
}
@@ -60,11 +60,16 @@ public enum Permission {
}
public boolean hasPermission(Player member) {
if (SteamwarUser.get(member.getUniqueId()).getId() == BauServer.getInstance().getOwnerID()) {
SteamwarUser steamwarUser = SteamwarUser.get(member.getUniqueId());
BauServer server = BauServer.getInstance();
if (!server.isTeamWorld() && steamwarUser.getId() == server.getOwnerID()) {
return this != SPECTATOR;
}
BauweltMember bauweltMember = BauweltMember.getBauMember(BauServer.getInstance().getOwner(), member.getUniqueId());
if (server.isTeamWorld() && steamwarUser.getTeam() == server.getTeamID()) {
return this != SPECTATOR;
}
BauweltMember bauweltMember = BauweltMember.getBauMember(server.getWorldID(), steamwarUser.getId());
if (bauweltMember == null) return this == SPECTATOR;
return permissionPredicate.test(bauweltMember);
}
}
}
@@ -34,16 +34,41 @@ public class BauServer {
}
private Integer owner;
private UUID world;
private Integer team;
public UUID getOwner() {
return SteamwarUser.byId(getOwnerID()).getUUID();
Integer ownerId = getOwnerID();
return ownerId == null ? null : SteamwarUser.byId(ownerId).getUUID();
}
public int getOwnerID() {
public Integer getOwnerID() {
//Lazy loading to improve startup time of the server in 1.15
if (owner == null) {
owner = BauServerInfo.getOwnerId();
}
return owner;
}
}
public boolean hasOwner() {
return getOwnerID() != null;
}
public UUID getWorldID() {
if (world == null) {
world = BauServerInfo.getWorldId();
}
return world;
}
public Integer getTeamID() {
if (team == null) {
team = BauServerInfo.getTeamId();
}
return team;
}
public boolean isTeamWorld() {
return getTeamID() != null;
}
}
@@ -47,7 +47,7 @@ public class BauInfoBauGuiItem extends BauGuiItem {
@Override
public ItemStack getItem(Player player) {
SWItem itemStack;
if (!player.getName().endsWith("")) {
if (!player.getName().endsWith("") && !BauServer.getInstance().isTeamWorld()) {
itemStack = SWItem.getPlayerSkull(SteamwarUser.get(BauServer.getInstance().getOwner()).getUserName());
} else {
itemStack = new SWItem(Material.PLAYER_HEAD, "");
@@ -47,7 +47,9 @@ public class InfoCommand extends SWCommand {
@Register(description = "BAU_INFO_COMMAND_HELP")
public void genericCommand(Player p) {
BauSystem.MESSAGE.send("BAU_INFO_COMMAND_OWNER", p, SteamwarUser.byId(bauServer.getOwnerID()).getUserName());
if (!bauServer.isTeamWorld()) {
BauSystem.MESSAGE.send("BAU_INFO_COMMAND_OWNER", p, SteamwarUser.byId(bauServer.getOwnerID()).getUserName());
}
Region region = Region.getRegion(p.getLocation());
for (Flag flag : Flag.getFlags()) {
if (!region.getRegionData().has(flag).isApplicable()) continue;
@@ -58,7 +60,7 @@ public class InfoCommand extends SWCommand {
}
if (Permission.BUILD.hasPermission(p)) {
List<BauweltMember> members = BauweltMember.getMembers(bauServer.getOwnerID());
List<BauweltMember> members = BauweltMember.getWorldMembers(bauServer.getWorldID());
Map<Permission, List<BauweltMember>> memberByPermission = new HashMap<>();
members.forEach(member -> {
if (Permission.SUPERVISOR.hasPermission(member)) {
@@ -1,135 +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.experimental.redstone_engine;
import de.steamwar.bausystem.features.experimental.ExperimentalCommand;
import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.utils.ScoreboardElement;
import de.steamwar.command.AbstractSWCommand;
import de.steamwar.command.SWCommand;
import de.steamwar.linkage.Linked;
import io.papermc.paper.configuration.WorldConfiguration;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.title.TitlePart;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import java.util.Collection;
import java.util.List;
@AbstractSWCommand.PartOf(ExperimentalCommand.class)
@Linked
public class RedstoneEngine extends SWCommand implements Listener, ScoreboardElement {
public RedstoneEngine() {
super("");
}
private WorldConfiguration.Misc getConfig() {
return ((CraftWorld) Bukkit.getWorlds().get(0)).getHandle().paperConfig().misc;
}
@Register("redstone")
@Register("redstoneengine")
public void setRedstoneEngine(Player player, @StaticValue("alternate_current") String __, WorldConfiguration.Misc.AlternateCurrentUpdateOrder updateOrder) {
WorldConfiguration.Misc misc = getConfig();
misc.redstoneImplementation = WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT;
misc.alternateCurrentUpdateOrder = updateOrder;
broadcastTitle(Bukkit.getOnlinePlayers());
}
@Register("redstone")
@Register("redstoneengine")
public void setRedstoneEngine(Player player, WorldConfiguration.Misc.RedstoneImplementation implementation) {
getConfig().redstoneImplementation = implementation;
broadcastTitle(Bukkit.getOnlinePlayers());
}
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
WorldConfiguration.Misc misc = getConfig();
if (misc.redstoneImplementation != WorldConfiguration.Misc.RedstoneImplementation.EIGENCRAFT) {
broadcastTitle(List.of(event.getPlayer()));
}
}
private void broadcastTitle(Collection<? extends Player> players) {
WorldConfiguration.Misc misc = getConfig();
Component title = switch (misc.redstoneImplementation) {
case VANILLA -> Component.text("").color(NamedTextColor.RED).append(Component.text(" Redstone: Vanilla ").color(NamedTextColor.WHITE)).append(Component.text("").color(NamedTextColor.RED));
case EIGENCRAFT -> Component.text("Redstone: Eigencraft");
case ALTERNATE_CURRENT -> Component.text("").color(NamedTextColor.RED).append(Component.text(" Redstone: AC ").color(NamedTextColor.WHITE)).append(Component.text("").color(NamedTextColor.RED));
};
Component subtitle;
if (misc.redstoneImplementation != WorldConfiguration.Misc.RedstoneImplementation.ALTERNATE_CURRENT) {
subtitle = Component.text().build();
} else {
subtitle = switch (misc.alternateCurrentUpdateOrder) {
case VERTICAL_FIRST_INWARD -> Component.text("Y first inwards"); // Y before XZ
case VERTICAL_FIRST_OUTWARD -> Component.text("Y first outwards"); // XZ before Y
case HORIZONTAL_FIRST_INWARD -> Component.text("XZ first inwards"); // Y before XZ
case HORIZONTAL_FIRST_OUTWARD -> Component.text("XZ first outwards"); // XZ before Y
};
}
players.forEach(player -> {
player.sendTitlePart(TitlePart.TITLE, title);
player.sendTitlePart(TitlePart.SUBTITLE, subtitle);
});
}
@Override
public ScoreboardGroup getGroup() {
return ScoreboardGroup.FOOTER;
}
@Override
public int order() {
return Integer.MAX_VALUE;
}
@Override
public String get(Region region, Player p) {
WorldConfiguration.Misc misc = getConfig();
switch (misc.redstoneImplementation) {
case ALTERNATE_CURRENT:
switch (misc.alternateCurrentUpdateOrder) {
case VERTICAL_FIRST_INWARD:
return "§eRedstone§8: §cAC §8(§7Y in§8)";
case VERTICAL_FIRST_OUTWARD:
return "§eRedstone§8: §cAC §8(§7Y out§8)";
case HORIZONTAL_FIRST_INWARD:
return "§eRedstone§8: §cAC §8(§7XZ in§8)";
case HORIZONTAL_FIRST_OUTWARD:
return "§eRedstone§8: §cAC §8(§7XZ out§8)";
}
return "§eRedstone§8: §cAC";
case EIGENCRAFT:
return null;
case VANILLA:
default:
return "§eRedstone§8: §cVanilla";
}
}
}
@@ -20,6 +20,7 @@
package de.steamwar.bausystem.features.region;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.Permission;
import de.steamwar.bausystem.config.BauServer;
import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.region.RegionUtils;
@@ -83,6 +84,9 @@ public class ColorCommand extends SWCommand {
@ClassValidator(value = Player.class, local = true)
public TypeValidator<Player> validator() {
return (commandSender, player, messageSender) -> {
if (bauServer.isTeamWorld()) {
return !messageSender.send(!Permission.SUPERVISOR.hasPermission(player), "NO_PERMISSION");
}
return !messageSender.send(!bauServer.getOwner().equals(player.getUniqueId()), "NO_PERMISSION");
};
}
@@ -68,7 +68,7 @@ public class ResetCommand extends SWCommand {
Region region = regionCheck(p);
if (region == null) return;
if (!p.getUniqueId().equals(bauServer.getOwner())) {
if (bauServer.hasOwner() && !p.getUniqueId().equals(bauServer.getOwner())) {
if (Punishment.isPunished(SteamwarUser.get(bauServer.getOwner()), Punishment.PunishmentType.NoSchemReceiving, punishment -> BauSystem.MESSAGE.send("REGION_TB_NO_SCHEMRECEIVING", p, punishment.getEndTime()))) {
return;
}
@@ -91,7 +91,7 @@ public class TestblockCommand extends SWCommand {
}
}
if (!p.getUniqueId().equals(bauServer.getOwner())) {
if (bauServer.hasOwner() && !p.getUniqueId().equals(bauServer.getOwner())) {
if (Punishment.isPunished(SteamwarUser.get(bauServer.getOwner()), Punishment.PunishmentType.NoSchemReceiving, punishment -> BauSystem.MESSAGE.send("REGION_TB_NO_SCHEMRECEIVING", p, punishment.getEndTime()))) {
return;
}
@@ -19,6 +19,7 @@
package de.steamwar.bausystem.features.simulator;
import com.comphenix.tinyprotocol.TinyProtocol;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.Permission;
import de.steamwar.bausystem.SWUtils;
@@ -37,80 +38,77 @@ import de.steamwar.bausystem.features.simulator.gui.SimulatorGui;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorBaseGui;
import de.steamwar.bausystem.utils.BauMemberUpdateEvent;
import de.steamwar.bausystem.utils.ItemUtils;
import de.steamwar.core.SWPlayer;
import de.steamwar.cursor.Cursor;
import de.steamwar.bausystem.utils.RayTraceUtils;
import de.steamwar.entity.REntity;
import de.steamwar.entity.REntityServer;
import de.steamwar.entity.RFallingBlockEntity;
import de.steamwar.inventory.SWAnvilInv;
import de.steamwar.linkage.Linked;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
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.inventory.ItemStack;
import org.bukkit.util.Vector;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
@Linked
public class SimulatorCursor implements Listener {
private static final Map<Player, CursorType> cursorType = Collections.synchronizedMap(new HashMap<>());
private static final Map<Player, REntityServer> emptyTargetServers = Collections.synchronizedMap(new HashMap<>());
private static final World WORLD = Bukkit.getWorlds().get(0);
private static Map<Player, CursorType> cursorType = Collections.synchronizedMap(new HashMap<>());
private static Map<Player, REntityServer> cursors = Collections.synchronizedMap(new HashMap<>());
private static final Set<Player> calculating = new HashSet<>();
public static boolean isSimulatorItem(ItemStack itemStack) {
return ItemUtils.isItem(itemStack, "simulator");
}
private static boolean hasSimulatorItem(Player player) {
return isSimulatorItem(player.getInventory().getItemInMainHand()) || isSimulatorItem(player.getInventory().getItemInOffHand());
}
private static void scheduleCursorUpdate(Player player) {
BauSystem.runTaskLater(BauSystem.getInstance(), () -> calcCursor(player), 1);
public SimulatorCursor() {
BiFunction<Player, ServerboundMovePlayerPacket, Object> function = (player, object) -> {
calcCursor(player);
return object;
};
TinyProtocol.instance.addFilter(ServerboundMovePlayerPacket.Pos.class, function);
TinyProtocol.instance.addFilter(ServerboundMovePlayerPacket.Rot.class, function);
TinyProtocol.instance.addFilter(ServerboundMovePlayerPacket.PosRot.class, function);
}
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
scheduleCursorUpdate(event.getPlayer());
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
calcCursor(event.getPlayer());
}, 0);
}
@EventHandler
public void onPlayerDropItem(PlayerDropItemEvent event) {
if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
scheduleCursorUpdate(event.getPlayer());
calcCursor(event.getPlayer());
}
@EventHandler
public void onPlayerItemHeld(PlayerItemHeldEvent event) {
if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
scheduleCursorUpdate(event.getPlayer());
}
@EventHandler
public void onInventoryClick(InventoryClickEvent event) {
if (!(event.getWhoClicked() instanceof Player player)) return;
if (!Permission.BUILD.hasPermission(player)) return;
scheduleCursorUpdate(player);
}
@EventHandler
public void onInventoryDrag(InventoryDragEvent event) {
if (!(event.getWhoClicked() instanceof Player player)) return;
if (!Permission.BUILD.hasPermission(player)) return;
scheduleCursorUpdate(player);
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
calcCursor(event.getPlayer());
}, 1);
}
@EventHandler
@@ -121,7 +119,10 @@ public class SimulatorCursor implements Listener {
@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
cursorType.remove(event.getPlayer());
removeCursor(event.getPlayer());
cursors.remove(event.getPlayer());
synchronized (calculating) {
calculating.remove(event.getPlayer());
}
}
private static final Map<Player, Long> LAST_SNEAKS = new HashMap<>();
@@ -137,7 +138,7 @@ public class SimulatorCursor implements Listener {
public void onPlayerToggleSneak(PlayerToggleSneakEvent event) {
if (!event.isSneaking()) return;
Player player = event.getPlayer();
if (!hasSimulatorItem(player)) {
if (!isSimulatorItem(player.getInventory().getItemInMainHand()) && !isSimulatorItem(player.getInventory().getItemInOffHand())) {
return;
}
if (LAST_SNEAKS.containsKey(player)) {
@@ -163,10 +164,17 @@ public class SimulatorCursor implements Listener {
}
public static void calcCursor(Player player) {
if (!Permission.BUILD.hasPermission(player) || !hasSimulatorItem(player)) {
if (removeCursor(player) | SimulatorWatcher.show(null, player)) {
synchronized (calculating) {
if (calculating.contains(player)) return;
calculating.add(player);
}
if (!Permission.BUILD.hasPermission(player) || (!isSimulatorItem(player.getInventory().getItemInMainHand()) && !isSimulatorItem(player.getInventory().getItemInOffHand()))) {
if (removeCursor(player) || SimulatorWatcher.show(null, player)) {
SWUtils.sendToActionbar(player, "");
}
synchronized (calculating) {
calculating.remove(player);
}
return;
}
@@ -175,98 +183,203 @@ public class SimulatorCursor implements Listener {
removeCursor(player);
SimulatorWatcher.show(null, player);
SWUtils.sendToActionbar(player, "§cGenerating Stab");
synchronized (calculating) {
calculating.remove(player);
}
return;
}
SimulatorWatcher.show(simulator, player);
Cursor cursor = getOrCreateCursor(player, simulator, cursorType.getOrDefault(player, CursorType.TNT));
cursor.renderDeduplicated();
}
private static Cursor getOrCreateCursor(Player player, Simulator simulator, CursorType type) {
REntityServer targetServer = simulator == null ? emptyTargetServers.computeIfAbsent(player, __ -> new REntityServer()) : SimulatorWatcher.getEntityServerOfSimulator(simulator);
SWPlayer swPlayer = SWPlayer.of(player);
Optional<Cursor> activeCursor = swPlayer.getComponent(Cursor.class);
Cursor cursor = activeCursor.orElse(null);
if (cursor == null || cursor.getTargetServer() != targetServer) {
swPlayer.removeComponent(Cursor.class);
cursor = new Cursor(
targetServer,
player,
Material.GLASS,
type.material,
type.cursorModes,
(location, hitEntity, action) -> handlePlayerClick(player, location, hitEntity, action),
(location, hitEntity) -> sendCursorActionbar(player, SimulatorStorage.getSimulator(player), location != null, hitEntity.isPresent())
);
} else {
cursor.setCursorMaterial(type.material);
cursor.setAllowedCursorModes(type.cursorModes);
List<REntity> entities = SimulatorWatcher.getEntitiesOfSimulator(simulator);
RayTraceUtils.RRayTraceResult rayTraceResult = RayTraceUtils.traceREntity(player, player.getLocation(), entities);
if (rayTraceResult == null) {
removeCursor(player);
if (simulator == null) {
SWUtils.sendToActionbar(player, "§eSelect Simulator");
} else {
SWUtils.sendToActionbar(player, "§eOpen Simulator");
}
synchronized (calculating) {
calculating.remove(player);
}
return;
}
return cursor;
showCursor(player, rayTraceResult, simulator != null);
synchronized (calculating) {
calculating.remove(player);
}
}
private static synchronized boolean removeCursor(Player player) {
Optional<Cursor> cursor = SWPlayer.of(player).getComponent(Cursor.class);
cursor.ifPresent(__ -> SWPlayer.of(player).removeComponent(Cursor.class));
REntityServer emptyTargetServer = emptyTargetServers.remove(player);
if (emptyTargetServer != null) {
emptyTargetServer.close();
REntityServer entityServer = cursors.get(player);
boolean hadCursor = entityServer != null && !entityServer.getEntities().isEmpty();
if (entityServer != null) {
entityServer.getEntities().forEach(REntity::die);
}
return cursor.isPresent();
return hadCursor;
}
private static void sendCursorActionbar(Player player, Simulator simulator, boolean hasCursorLocation, boolean hasHitEntity) {
if (!hasCursorLocation) {
SWUtils.sendToActionbar(player, simulator == null ? "§eSelect Simulator" : "§eOpen Simulator");
} else if (simulator == null) {
SWUtils.sendToActionbar(player, "§eCreate new Simulator");
} else if (hasHitEntity) {
SWUtils.sendToActionbar(player, "§eEdit Position");
} else {
SWUtils.sendToActionbar(player, "§eAdd new " + cursorType.getOrDefault(player, CursorType.TNT).name);
private static synchronized void showCursor(Player player, RayTraceUtils.RRayTraceResult rayTraceResult, boolean hasSimulatorSelected) {
REntityServer entityServer = cursors.computeIfAbsent(player, __ -> {
REntityServer rEntityServer = new REntityServer();
rEntityServer.addPlayer(player);
return rEntityServer;
});
CursorType type = cursorType.getOrDefault(player, CursorType.TNT);
REntity hitEntity = rayTraceResult.getHitEntity();
Location location = hitEntity != null ? new Vector(hitEntity.getX(), hitEntity.getY(), hitEntity.getZ()).toLocation(WORLD) :
type.position.apply(player, rayTraceResult).toLocation(WORLD);
Material material = hitEntity != null ? Material.GLASS : type.getMaterial();
List<RFallingBlockEntity> entities = entityServer.getEntitiesByType(RFallingBlockEntity.class);
entities.removeIf(rFallingBlockEntity -> {
if (rFallingBlockEntity.getMaterial() != material) {
rFallingBlockEntity.die();
return true;
}
rFallingBlockEntity.move(location);
return false;
});
if (entities.isEmpty()) {
RFallingBlockEntity rFallingBlockEntity = new RFallingBlockEntity(entityServer, location, material);
rFallingBlockEntity.setNoGravity(true);
if (material == Material.GLASS) {
rFallingBlockEntity.setGlowing(true);
}
}
if (hasSimulatorSelected) {
if (hitEntity != null) {
SWUtils.sendToActionbar(player, "§eEdit Position");
} else {
SWUtils.sendToActionbar(player, "§eAdd new " + type.name);
}
} else {
SWUtils.sendToActionbar(player, "§eCreate new Simulator");
}
}
public static Vector getPosFree(Player player, RayTraceUtils.RRayTraceResult result) {
Vector pos = result.getHitPosition();
BlockFace face = result.getHitBlockFace();
if (face != null) {
switch (face) {
case DOWN:
pos.setY(pos.getY() - 0.98);
break;
case EAST:
pos.setX(pos.getX() + 0.49);
break;
case WEST:
pos.setX(pos.getX() - 0.49);
break;
case NORTH:
pos.setZ(pos.getZ() - 0.49);
break;
case SOUTH:
pos.setZ(pos.getZ() + 0.49);
break;
default:
break;
}
if (face.getModY() == 0 && player.isSneaking()) {
pos.setY(pos.getY() - 0.49);
}
}
if (!player.isSneaking()) {
pos.setX(pos.getBlockX() + 0.5);
if (face == null || face.getModY() == 0) {
pos.setY(pos.getBlockY() + 0.0);
}
pos.setZ(pos.getBlockZ() + 0.5);
}
return pos;
}
private static Vector getPosBlockAligned(Player player, RayTraceUtils.RRayTraceResult result) {
Vector pos = result.getHitPosition();
BlockFace face = result.getHitBlockFace();
if (face != null) {
switch (face) {
case DOWN:
pos.setY(pos.getY() - 0.98);
break;
case EAST:
pos.setX(pos.getX() + 0.49);
break;
case WEST:
pos.setX(pos.getX() - 0.49);
break;
case NORTH:
pos.setZ(pos.getZ() - 0.49);
break;
case SOUTH:
pos.setZ(pos.getZ() + 0.49);
break;
default:
break;
}
}
pos.setX(pos.getBlockX() + 0.5);
if (pos.getY() - pos.getBlockY() != 0 && face == BlockFace.UP) {
pos.setY(pos.getBlockY() + 1.0);
} else {
pos.setY(pos.getBlockY());
}
pos.setZ(pos.getBlockZ() + 0.5);
return pos;
}
@Getter
@AllArgsConstructor
public enum CursorType {
TNT(Material.TNT, Material.GUNPOWDER, List.of(Cursor.CursorMode.FREE, Cursor.CursorMode.SURFACE_ALIGNED), "TNT", vector -> new TNTElement(vector).add(new TNTPhase())),
REDSTONE_BLOCK(Material.REDSTONE_BLOCK, Material.REDSTONE, List.of(Cursor.CursorMode.BLOCK_ALIGNED), "Redstone Block", vector -> new RedstoneElement(vector).add(new RedstonePhase())),
OBSERVER(Material.OBSERVER, Material.QUARTZ, List.of(Cursor.CursorMode.BLOCK_ALIGNED), "Observer", vector -> new ObserverElement(vector).add(new ObserverPhase())),
TNT(Material.TNT, Material.GUNPOWDER, SimulatorCursor::getPosFree, "TNT", vector -> new TNTElement(vector).add(new TNTPhase())),
REDSTONE_BLOCK(Material.REDSTONE_BLOCK, Material.REDSTONE, SimulatorCursor::getPosBlockAligned, "Redstone Block", vector -> new RedstoneElement(vector).add(new RedstonePhase())),
OBSERVER(Material.OBSERVER, Material.QUARTZ, SimulatorCursor::getPosBlockAligned, "Observer", vector -> new ObserverElement(vector).add(new ObserverPhase())),
;
public final Material material;
public final Material nonSelectedMaterial;
public final List<Cursor.CursorMode> cursorModes;
public final BiFunction<Player, RayTraceUtils.RRayTraceResult, Vector> position;
public final String name;
public final Function<Vector, SimulatorElement<?>> elementFunction;
}
private static void handlePlayerClick(Player player, Location cursorLocation, Optional<REntity> hitEntity, Action action) {
if (!Permission.BUILD.hasPermission(player)) return;
if (!hasSimulatorItem(player)) {
@EventHandler
public void onPlayerInteract(PlayerInteractEvent event) {
if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
if (!ItemUtils.isItem(event.getItem(), "simulator")) {
return;
}
event.setCancelled(true);
Player player = event.getPlayer();
Simulator simulator = SimulatorStorage.getSimulator(player);
if (action == Action.LEFT_CLICK_BLOCK || action == Action.LEFT_CLICK_AIR) {
if (event.getAction() == Action.LEFT_CLICK_BLOCK || event.getAction() == Action.LEFT_CLICK_AIR) {
if (simulator == null) {
return;
}
SimulatorExecutor.run(player, simulator, null);
SimulatorExecutor.run(event.getPlayer(), simulator, null);
return;
}
if (action != Action.RIGHT_CLICK_BLOCK && action != Action.RIGHT_CLICK_AIR) {
if (event.getAction() != Action.RIGHT_CLICK_BLOCK && event.getAction() != Action.RIGHT_CLICK_AIR) {
return;
}
RayTraceUtils.RRayTraceResult rayTraceResult = RayTraceUtils.traceREntity(player, player.getLocation(), SimulatorWatcher.getEntitiesOfSimulator(simulator));
if (simulator == null) {
if (cursorLocation == null) {
if (rayTraceResult == null) {
SimulatorStorage.openSimulatorSelector(player);
} else {
SWAnvilInv anvilInv = new SWAnvilInv(player, "Name");
@@ -282,7 +395,7 @@ public class SimulatorCursor implements Listener {
}
sim = new Simulator(s);
SimulatorStorage.addSimulator(s, sim);
createElement(player, cursorLocation, sim);
createElement(player, rayTraceResult, sim);
SimulatorStorage.setSimulator(player, sim);
});
anvilInv.open();
@@ -290,56 +403,57 @@ public class SimulatorCursor implements Listener {
return;
}
if (cursorLocation == null) {
if (rayTraceResult == null) {
new SimulatorGui(player, simulator).open();
return;
}
if (hitEntity.isPresent()) {
openElement(player, simulator, hitEntity.get());
if (rayTraceResult.getHitEntity() != null) {
REntity hitEntity = rayTraceResult.getHitEntity();
Vector vector = new Vector(hitEntity.getX(), hitEntity.getY(), hitEntity.getZ());
List<SimulatorElement<?>> elements = simulator.getGroups().stream().map(SimulatorGroup::getElements).flatMap(List::stream).filter(element -> {
return element.getWorldPos().distanceSquared(vector) < (1 / 16.0) * (1 / 16.0);
}).collect(Collectors.toList());
switch (elements.size()) {
case 0:
return;
case 1:
// Open single element present in Simulator
SimulatorElement<?> element = elements.get(0);
SimulatorGroup group1 = element.getGroup(simulator);
SimulatorBaseGui back = new SimulatorGui(player, simulator);
if (group1.getElements().size() > 1) {
back = new SimulatorGroupGui(player, simulator, group1, back);
}
element.open(player, simulator, group1, back);
break;
default:
List<SimulatorGroup> parents = elements.stream().map(e -> e.getGroup(simulator)).distinct().collect(Collectors.toList());
if (parents.size() == 1) {
// Open multi element present in Simulator in existing group
SimulatorGui simulatorGui = new SimulatorGui(player, simulator);
new SimulatorGroupGui(player, simulator, parents.get(0), simulatorGui).open();
} else {
// Open multi element present in Simulator in implicit group
SimulatorGroup group2 = new SimulatorGroup();
group2.setMaterial(null);
group2.getElements().addAll(elements);
SimulatorGui simulatorGui = new SimulatorGui(player, simulator);
new SimulatorGroupGui(player, simulator, group2, simulatorGui).open();
}
break;
}
return;
}
createElement(player, cursorLocation, simulator);
// Add new Element to current simulator
createElement(player, rayTraceResult, simulator);
}
private static void openElement(Player player, Simulator simulator, REntity hitEntity) {
Vector vector = new Vector(hitEntity.getX(), hitEntity.getY(), hitEntity.getZ());
List<SimulatorElement<?>> elements = simulator.getGroups().stream().map(SimulatorGroup::getElements).flatMap(List::stream).filter(element -> {
return element.getWorldPos().distanceSquared(vector) < (1 / 16.0) * (1 / 16.0);
}).collect(Collectors.toList());
switch (elements.size()) {
case 0:
return;
case 1:
SimulatorElement<?> element = elements.get(0);
SimulatorGroup group1 = element.getGroup(simulator);
SimulatorBaseGui back = new SimulatorGui(player, simulator);
if (group1.getElements().size() > 1) {
back = new SimulatorGroupGui(player, simulator, group1, back);
}
element.open(player, simulator, group1, back);
break;
default:
List<SimulatorGroup> parents = elements.stream().map(e -> e.getGroup(simulator)).distinct().collect(Collectors.toList());
if (parents.size() == 1) {
SimulatorGui simulatorGui = new SimulatorGui(player, simulator);
new SimulatorGroupGui(player, simulator, parents.get(0), simulatorGui).open();
} else {
SimulatorGroup group2 = new SimulatorGroup();
group2.setMaterial(null);
group2.getElements().addAll(elements);
SimulatorGui simulatorGui = new SimulatorGui(player, simulator);
new SimulatorGroupGui(player, simulator, group2, simulatorGui).open();
}
break;
}
}
private static void createElement(Player player, Location cursorLocation, Simulator simulator) {
private void createElement(Player player, RayTraceUtils.RRayTraceResult rayTraceResult, Simulator simulator) {
CursorType type = cursorType.getOrDefault(player, CursorType.TNT);
Vector vector = cursorLocation.toVector();
Vector vector = type.position.apply(player, rayTraceResult);
if (type == CursorType.REDSTONE_BLOCK) {
vector.subtract(new Vector(0.5, 0, 0.5));
}
@@ -124,11 +124,4 @@ public class SimulatorWatcher {
}
return entityServer.getEntities();
}
synchronized REntityServer getEntityServerOfSimulator(Simulator simulator) {
if (simulator == null) {
return null;
}
return entityServers.computeIfAbsent(simulator, __ -> createSim(new REntityServer(), simulator));
}
}
@@ -1,72 +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.slaves.blastresistance;
import com.google.common.util.concurrent.AtomicDouble;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.world.block.BlockState;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.command.SWCommand;
import de.steamwar.linkage.Linked;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import java.util.concurrent.atomic.AtomicInteger;
@Linked
public class BlastResistanceCommand extends SWCommand {
public BlastResistanceCommand() {
super("blastresistance");
}
@Register(description = "BLASTRESISTANCE_HELP")
public void command(@Validator Player player) {
LocalSession localSession = WorldEdit.getInstance().getSessionManager().get(BukkitAdapter.adapt(player));
Clipboard clipboard;
try {
clipboard = localSession.getClipboard().getClipboards().getFirst();
} catch (WorldEditException e) {
BauSystem.MESSAGE.send("BLASTRESISTANCE_NO_CLIPBOARD", player);
return;
}
AtomicDouble min = new AtomicDouble(0);
AtomicDouble max = new AtomicDouble(0);
AtomicDouble sum = new AtomicDouble(0);
AtomicInteger count = new AtomicInteger(0);
clipboard.forEach(blockVector3 -> {
BlockState blockState = clipboard.getBlock(blockVector3);
Material material = BukkitAdapter.adapt(blockState).getMaterial();
if (material == Material.WATER || material == Material.LAVA) return;
double blastResistance = BukkitAdapter.adapt(blockState).getMaterial().getBlastResistance();
min.set(Math.min(min.get(), blastResistance));
max.set(Math.max(max.get(), blastResistance));
sum.addAndGet(blastResistance);
count.getAndIncrement();
});
BauSystem.MESSAGE.send("BLASTRESISTANCE_RESULT", player, min.get(), max.get(), sum.get() / count.get());
}
}
@@ -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)
@@ -20,6 +20,7 @@
package de.steamwar.bausystem.features.world;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.Permission;
import de.steamwar.bausystem.config.BauServer;
import de.steamwar.core.CRIUWakeupEvent;
import de.steamwar.linkage.Linked;
@@ -36,12 +37,13 @@ public class AntiBauAddMemberFix implements Listener {
@EventHandler(priority = EventPriority.LOW)
public void onPlayerJoin(PlayerJoinEvent event) {
if (BauSystem.DEV_SERVER) return;
if (event.getPlayer().getUniqueId().equals(BauServer.getInstance().getOwner())) {
if (Permission.SUPERVISOR.hasPermission(event.getPlayer())) {
return;
}
if (BauweltMember.getBauMember(BauServer.getInstance().getOwner(), event.getPlayer().getUniqueId()) == null) {
if (BauweltMember.getBauMember(BauServer.getInstance().getWorldID(), event.getPlayer().getUniqueId()) == null) {
event.getPlayer().kickPlayer("");
throw new SecurityException("The player " + event.getPlayer().getName() + " joined on the server of " + SteamwarUser.byId(BauServer.getInstance().getOwnerID()).getUserName() + " without being added!");
String owner = BauServer.getInstance().isTeamWorld() ? "team " + BauServer.getInstance().getTeamID() : SteamwarUser.byId(BauServer.getInstance().getOwnerID()).getUserName();
throw new SecurityException("The player " + event.getPlayer().getName() + " joined on the server of " + owner + " without being added!");
}
}
@@ -33,7 +33,7 @@ public class AxiomPermissionCheck implements Listener {
@EventHandler
public void onAxiomHandshake(AxiomHandshakeEvent event) {
if (Permission.SUPERVISOR.hasPermission(event.getPlayer()) || BauServer.getInstance().getOwner().equals(event.getPlayer().getUniqueId())) {
if (Permission.SUPERVISOR.hasPermission(event.getPlayer()) || (BauServer.getInstance().hasOwner() && BauServer.getInstance().getOwner().equals(event.getPlayer().getUniqueId()))) {
event.setMaxBufferSize(Short.MAX_VALUE);
return;
}
@@ -28,7 +28,7 @@ import de.steamwar.data.BauLockState;
import de.steamwar.linkage.Linked;
import de.steamwar.network.packets.PacketHandler;
import de.steamwar.network.packets.server.BaulockUpdatePacket;
import de.steamwar.sql.UserConfig;
import de.steamwar.sql.SteamwarWorld;
import lombok.Getter;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@@ -37,13 +37,13 @@ import org.bukkit.event.Listener;
@Linked
public class BauLockStateScoreboard extends PacketHandler implements ScoreboardElement, Listener {
private static final String BAU_LOCK_CONFIG_NAME = "baulockstate";
@Getter
private BauLockState lockState = loadLockState();
private BauLockState loadLockState() {
String state = UserConfig.getConfig(BauServer.getInstance().getOwner(), BAU_LOCK_CONFIG_NAME);
if (BauServer.getInstance().isTeamWorld()) return BauLockState.OPEN;
SteamwarWorld world = SteamwarWorld.getWorld(BauServer.getInstance().getWorldID());
String state = world == null ? null : world.getLockState();
return state == null ? BauLockState.OPEN : BauLockState.valueOf(state);
}
@@ -69,6 +69,7 @@ public class BauLockStateScoreboard extends PacketHandler implements ScoreboardE
@Override
public String get(Region region, Player p) {
if (BauServer.getInstance().isTeamWorld()) return null;
if (!BauServer.getInstance().getOwner().equals(p.getUniqueId())) {
return null;
}
@@ -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;
@@ -42,7 +42,11 @@ public class KickallCommand extends SWCommand {
if (!Permission.OWNER.hasPermission(player)) return;
Bukkit.getOnlinePlayers().forEach(p -> {
if (!bauServer.getOwner().equals(p.getUniqueId())) p.kickPlayer("");
if (bauServer.isTeamWorld()) {
if (!Permission.SUPERVISOR.hasPermission(p)) p.kickPlayer("");
} else if (!bauServer.getOwner().equals(p.getUniqueId())) {
p.kickPlayer("");
}
});
}
}
@@ -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;
@@ -143,17 +143,14 @@ public class SpectatorListener implements Listener {
public void onPlayerJoin(PlayerJoinEvent event) {
enableOrDisableTechhider();
if (BauSystem.DEV_SERVER) return;
if (event.getPlayer().getUniqueId().equals(BauServer.getInstance().getOwner())) {
if (Permission.SUPERVISOR.hasPermission(event.getPlayer())) {
return;
}
BauweltMember bauweltMember = BauweltMember.getBauMember(BauServer.getInstance().getOwner(), event.getPlayer().getUniqueId());
BauweltMember bauweltMember = BauweltMember.getBauMember(BauServer.getInstance().getWorldID(), event.getPlayer().getUniqueId());
if (bauweltMember == null) {
event.getPlayer().kickPlayer("");
return;
}
if (Permission.SUPERVISOR.hasPermission(event.getPlayer())) {
return;
}
if (!anySupervisorOnline(null)) {
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
event.getPlayer().kickPlayer("");
@@ -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;
@@ -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()) {
@@ -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
@@ -17,7 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.cursor;
package de.steamwar.bausystem.utils;
import de.steamwar.entity.REntity;
import lombok.Data;
@@ -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
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 -1
View File
@@ -11,13 +11,23 @@ import de.steamwar.commands.profiler.ProfilerCommand
import de.steamwar.commands.user.UserCommand
import de.steamwar.commands.user.UserInfoCommand
import de.steamwar.commands.user.UserSearchCommand
import de.steamwar.commands.world.MigrationCommand
import de.steamwar.commands.world.SaveToStorageCommand
import de.steamwar.commands.world.TemplateCommand
import de.steamwar.commands.world.TemplateCreateCommand
import de.steamwar.commands.world.WorldCommand
fun main(args: Array<String>) =
SteamWar()
.subcommands(
DatabaseCommand().subcommands(InfoCommand(), ResetCommand()),
UserCommand().subcommands(UserInfoCommand(), UserSearchCommand()),
WorldCommand().subcommands(
MigrationCommand(),
SaveToStorageCommand(),
TemplateCommand().subcommands(TemplateCreateCommand())
),
DevCommand(),
ProfilerCommand()
)
.main(args)
.main(args)
+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()
}
}
}
+243
View File
@@ -0,0 +1,243 @@
package de.steamwar.commands.world
import com.github.ajalt.clikt.core.CliktCommand
import de.steamwar.db.Database
import de.steamwar.sql.SteamwarUser
import de.steamwar.sql.SteamwarWorld
import de.steamwar.sql.UserConfig
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
import java.io.File
import java.sql.SQLException
import java.util.UUID
class MigrationCommand : CliktCommand(name = "migrate") {
private val userWorldPattern = Regex("""userworlds(\d+)""")
private val builderWorldPattern = Regex("""builder(\d+)""")
private val trailingVersionPattern = Regex("""(\d+)$""")
override fun run() {
Database.ensureConnected()
val bau = migrateBauWorlds()
val builder = migrateBuilderWorlds()
val arena = migrateArenaWorlds()
migrateBauLockConfig()
migrateBauweltMembers()
echo("Imported ${bau.imported} build worlds, skipped ${bau.skipped}, missing users ${bau.missingOwner}.")
echo("Imported ${builder.imported} builder worlds, skipped ${builder.skipped}.")
echo("Imported ${arena.imported} arena worlds, skipped ${arena.skipped}.")
echo("World migration completed.")
}
private fun migrateBauWorlds(): MigrationStats {
val stats = MigrationStats()
val worldsRoot = File("/worlds")
worldsRoot.listFiles { file -> file.isDirectory && userWorldPattern.matches(file.name) }
?.forEach { versionFolder ->
val version = userWorldPattern.matchEntire(versionFolder.name)!!.groupValues[1].toInt()
versionFolder.listFiles { file -> file.isDirectory && isWorldDirectory(file) }
?.sortedBy { it.name }
?.forEach { legacyWorld ->
val owner = legacyWorld.ownerUser()
if (owner == null) {
stats.missingOwner++
echo("Skipped build world ${legacyWorld.path}: no matching user.")
return@forEach
}
val lockState = UserConfig.getConfig(owner.id.value, BAU_LOCK_CONFIG_NAME)
val world = SteamwarWorld.getOrCreateBauWorld(owner, owner.userName, version, legacyWorld)
if (world.lockState == null && lockState != null) {
world.changeLockState(lockState)
}
stats.imported++
}
} ?: run {
stats.skipped++
echo("Skipped build worlds: /worlds does not exist.")
}
return stats
}
private fun migrateBuilderWorlds(): MigrationStats {
val stats = MigrationStats()
val worldsRoot = File("/worlds")
worldsRoot.listFiles { file -> file.isDirectory && builderWorldPattern.matches(file.name) }
?.forEach { versionFolder ->
val version = builderWorldPattern.matchEntire(versionFolder.name)!!.groupValues[1].toInt()
versionFolder.listFiles { file -> file.isDirectory && isWorldDirectory(file) }
?.sortedBy { it.name }
?.forEach { legacyWorld ->
SteamwarWorld.getOrCreateBuilderWorld(legacyWorld.name, version, legacyWorld)
stats.imported++
}
} ?: run {
stats.skipped++
echo("Skipped builder worlds: /worlds does not exist.")
}
return stats
}
private fun migrateArenaWorlds(): MigrationStats {
val stats = MigrationStats()
val configs = parseGameModeConfigs(File("/configs/GameModes"))
if (configs.isEmpty()) {
stats.skipped++
echo("Skipped arena worlds: no game mode configs found in /configs/GameModes.")
return stats
}
for (config in configs) {
val version = trailingVersionPattern.find(config.serverFolder)?.value?.toIntOrNull()
if (version == null) {
stats.skipped++
echo("Skipped arena mode ${config.modeName}: server folder '${config.serverFolder}' has no version suffix.")
continue
}
val arenaRoot = File("/servers/${config.serverFolder}/arenas")
for (map in config.maps) {
val legacyWorld = File(arenaRoot, map)
if (!isWorldDirectory(legacyWorld)) {
stats.skipped++
continue
}
SteamwarWorld.getOrCreateArenaWorld(config.modeName, map, version, legacyWorld)
stats.imported++
}
}
return stats
}
private fun migrateBauLockConfig() {
transaction(Database.db) {
exec(
"""
UPDATE `world` w
JOIN `UserConfig` uc ON uc.`User` = w.`Owner` AND uc.`Config` = 'baulockstate'
SET w.`LockState` = uc.`Value`
WHERE w.`Owner` IS NOT NULL AND w.`LockState` IS NULL
""".trimIndent()
)
}
}
private fun migrateBauweltMembers() {
if (!columnExists("BauweltMember", "WorldID") || !columnExists("BauweltMember", "BauweltID")) {
echo("Skipped BauweltMember data migration: expected WorldID and BauweltID columns are not both present.")
return
}
execIgnore(
"""
INSERT IGNORE INTO `BauweltMember` (`WorldID`, `MemberID`, `Build`, `WorldEdit`, `World`)
SELECT w.`id`, bm.`MemberID`, bm.`Build`, bm.`WorldEdit`, bm.`World`
FROM `BauweltMember` bm
JOIN `world` w ON w.`Owner` = bm.`BauweltID` AND w.`Deleted` = 0
""".trimIndent(),
"Skipped BauweltMember data migration"
)
}
private fun parseGameModeConfigs(configRoot: File): List<ArenaMigrationConfig> {
if (!configRoot.isDirectory) return emptyList()
return configRoot.listFiles { file -> file.isFile && file.extension == "yml" && !file.name.endsWith(".kits.yml") }
?.mapNotNull { file ->
val server = parseServerBlock(file)
val folder = server.folder ?: return@mapNotNull null
ArenaMigrationConfig(file.nameWithoutExtension, folder, server.maps)
}
?: emptyList()
}
private fun parseServerBlock(file: File): ParsedServerBlock {
var inServer = false
var inMaps = false
var folder: String? = null
val maps = mutableListOf<String>()
file.readLines().forEach { line ->
val trimmed = line.trim()
if (trimmed.isEmpty() || trimmed.startsWith("#")) return@forEach
val indent = line.indexOfFirst { !it.isWhitespace() }.let { if (it == -1) 0 else it }
if (indent == 0) {
inServer = trimmed == "Server:"
inMaps = false
return@forEach
}
if (!inServer) return@forEach
when {
indent <= 2 && trimmed.startsWith("Folder:") -> folder = trimmed.substringAfter(":").cleanYamlValue()
indent <= 2 && trimmed.startsWith("Maps:") -> {
val value = trimmed.substringAfter(":").trim()
inMaps = true
if (value.startsWith("[") && value.endsWith("]")) {
maps += value.removePrefix("[").removeSuffix("]")
.split(",")
.map { it.cleanYamlValue() }
.filter { it.isNotEmpty() }
inMaps = false
}
}
inMaps && trimmed.startsWith("-") -> maps += trimmed.removePrefix("-").cleanYamlValue()
inMaps && indent <= 2 -> inMaps = false
}
}
return ParsedServerBlock(folder, maps)
}
private fun File.ownerUser(): SteamwarUser? {
name.toIntOrNull()?.let { return SteamwarUser.byId(it) }
return runCatching { SteamwarUser.get(UUID.fromString(name)) }.getOrNull()
}
private fun isWorldDirectory(file: File): Boolean =
file.isDirectory && (File(file, "level.dat").isFile || File(file, "region").isDirectory)
private fun String.cleanYamlValue(): String =
trim().trim('"').trim('\'')
private fun columnExists(table: String, column: String): Boolean =
transaction(Database.db) {
exec("SHOW COLUMNS FROM `$table` LIKE '$column'") { result -> result.next() } ?: false
}
private fun execIgnore(sql: String, prefix: String) {
try {
transaction(Database.db) {
exec(sql)
}
} catch (e: SQLException) {
echo("$prefix: ${e.message}")
}
}
private data class MigrationStats(
var imported: Int = 0,
var skipped: Int = 0,
var missingOwner: Int = 0,
)
private data class ArenaMigrationConfig(
val modeName: String,
val serverFolder: String,
val maps: List<String>,
)
private data class ParsedServerBlock(
val folder: String?,
val maps: List<String>,
)
private companion object {
private const val BAU_LOCK_CONFIG_NAME = "baulockstate"
}
}
@@ -0,0 +1,30 @@
package de.steamwar.commands.world
import com.github.ajalt.clikt.core.CliktCommand
import de.steamwar.db.Database
import de.steamwar.sql.SteamwarWorld
import java.time.LocalTime
class SaveToStorageCommand : CliktCommand(name = "saveToStorage") {
override fun run() {
Database.ensureConnected()
var archived = 0
for (world in SteamwarWorld.getWorldsToArchive()) {
if (!beforeCutoff()) {
echo("Stopping before 04:00. Archived $archived worlds.")
return
}
if (world.archiveIfNeeded()) {
archived++
echo("Archived ${world.uuid} (${world.type}/${world.name}).")
}
}
echo("Storage save completed. Archived $archived worlds.")
}
private fun beforeCutoff(): Boolean =
LocalTime.now().isBefore(LocalTime.of(4, 0))
}
+24
View File
@@ -0,0 +1,24 @@
package de.steamwar.commands.world
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.types.int
import com.github.ajalt.clikt.parameters.types.path
import de.steamwar.db.Database
import de.steamwar.sql.SteamwarWorld
class TemplateCommand : CliktCommand(name = "template") {
override fun run() = Unit
}
class TemplateCreateCommand : CliktCommand(name = "create") {
private val name by argument()
private val version by argument().int()
private val source by argument().path(canBeFile = false, mustExist = true)
override fun run() {
Database.ensureConnected()
SteamwarWorld.getOrCreateTemplateWorld(name, version, source.toFile())
echo("Template world '$name' created.")
}
}
+7
View File
@@ -0,0 +1,7 @@
package de.steamwar.commands.world
import com.github.ajalt.clikt.core.CliktCommand
class WorldCommand : CliktCommand(name = "world") {
override fun run() = Unit
}
@@ -19,7 +19,6 @@
package de.steamwar.sql
import de.steamwar.sql.BauweltMemberTable.bauweltId
import de.steamwar.sql.internal.useDb
import org.jetbrains.exposed.v1.core.and
import org.jetbrains.exposed.v1.core.dao.id.CompositeID
@@ -32,45 +31,37 @@ import org.jetbrains.exposed.v1.jdbc.insertIgnore
import java.util.*
object BauweltMemberTable : CompositeIdTable("BauweltMember") {
val bauweltId = reference("BauweltID", SteamwarUserTable).index()
val worldId = reference("WorldID", WorldTable).index()
val memberId = reference("MemberID", SteamwarUserTable).index()
val build = bool("Build")
val worldEdit = bool("WorldEdit")
val world = bool("World")
override val primaryKey = PrimaryKey(bauweltId, memberId)
override val primaryKey = PrimaryKey(worldId, memberId)
init {
addIdColumn(bauweltId)
addIdColumn(worldId)
addIdColumn(memberId)
}
}
class BauweltMember(id: EntityID<CompositeID>) : CompositeEntity(id) {
companion object : CompositeEntityClass<BauweltMember>(BauweltMemberTable) {
private val cache = mutableMapOf<Int, BauweltMember>()
private val cache = mutableMapOf<Pair<UUID, Int>, BauweltMember>()
private fun cache(member: BauweltMember) =
cache.put(member.memberID, member)
cache.put(member.worldID to member.memberID, member)
@JvmStatic
fun clear() =
cache.clear()
@JvmStatic
@Deprecated("Use addMember(ownerId: Int, newMemberId: Int)")
fun addMember(ownerId: UUID, newMemberId: UUID) =
addMember(SteamwarUser.get(ownerId)!!.id, SteamwarUser.get(newMemberId)!!.id)
@JvmStatic
fun addMember(ownerId: Int, newMemberId: Int) =
addMember(EntityID(ownerId, SteamwarUserTable), EntityID(newMemberId, SteamwarUserTable))
fun addMember(ownerId: EntityID<Int>, newMemberId: EntityID<Int>) =
fun addMember(worldId: UUID, newMemberId: Int) =
useDb {
BauweltMemberTable.insertIgnore {
it[bauweltId] = ownerId
it[memberId] = newMemberId
it[BauweltMemberTable.worldId] = EntityID(worldId, WorldTable)
it[memberId] = EntityID(newMemberId, SteamwarUserTable)
it[build] = false
it[worldEdit] = false
it[world] = false
@@ -78,31 +69,32 @@ class BauweltMember(id: EntityID<CompositeID>) : CompositeEntity(id) {
}
@JvmStatic
@Deprecated("Use getBauMember(bauwelt: Int, member: Int)")
fun getBauMember(bauwelt: UUID, member: UUID) =
fun addMember(worldId: UUID, newMemberId: UUID) =
addMember(worldId, SteamwarUser.get(newMemberId)!!.id.value)
@JvmStatic
fun getBauMember(world: UUID, member: Int) =
cache[world to member]
?: useDb {
find { (BauweltMemberTable.worldId eq world) and (BauweltMemberTable.memberId eq member) }.firstOrNull()?.also { cache(it) }
}
@JvmStatic
fun getBauMember(world: UUID, member: UUID) =
getBauMember(world, SteamwarUser.get(member)!!.id.value)
@JvmStatic
fun getWorldMembers(world: UUID) =
useDb {
find { (bauweltId eq SteamwarUser.get(bauwelt)!!.id) and (BauweltMemberTable.memberId eq SteamwarUser.get(member)!!.id) }.firstOrNull()?.also { cache(it) }
find { BauweltMemberTable.worldId eq world }.toList().also { it.forEach { cache(it) } }
}
@JvmStatic
fun getBauMember(bauwelt: Int, member: Int) =
useDb {
find { (bauweltId eq bauwelt) and (BauweltMemberTable.memberId eq member) }.firstOrNull()?.also { cache(it) }
}
@JvmStatic
@Deprecated("Use getMembers(bauwelt: Int)")
fun getMembers(bauwelt: UUID) =
getMembers(SteamwarUser.get(bauwelt)!!.id.value)
@JvmStatic
fun getMembers(bauwelt: Int) =
useDb {
find { bauweltId eq bauwelt }.toList().also { it.forEach { cache(it) } }
}
fun getMembers(world: UUID) =
getWorldMembers(world)
}
val bauweltID by BauweltMemberTable.bauweltId.transform({ EntityID(it, SteamwarUserTable) }, { it.value })
val worldID by BauweltMemberTable.worldId.transform({ EntityID(it, WorldTable) }, { it.value })
val memberID by BauweltMemberTable.memberId.transform({ EntityID(it, SteamwarUserTable) }, { it.value })
private var worldEditInternal by BauweltMemberTable.worldEdit
var worldEdit: Boolean
@@ -144,4 +136,4 @@ class BauweltMember(id: EntityID<CompositeID>) : CompositeEntity(id) {
useDb {
delete()
}
}
}
@@ -50,6 +50,8 @@ enum class UserPerm {
PUNISHMENTS,
TICKET_LOG,
BUILD,
BUILD_EXTRA_WORLDS,
BUILD_UNLIMITED_WORLDS,
CHECK,
MODERATION,
ADMINISTRATION;
@@ -95,4 +97,4 @@ enum class UserPerm {
}
data class Prefix(val colorCode: String, val chatPrefix: String, val teamPrefix: Boolean = false)
}
}
+345
View File
@@ -0,0 +1,345 @@
/*
* 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.sql
import de.steamwar.sql.internal.useDb
import org.jetbrains.exposed.v1.core.and
import org.jetbrains.exposed.v1.core.dao.id.EntityID
import org.jetbrains.exposed.v1.core.dao.id.java.UUIDTable
import org.jetbrains.exposed.v1.core.eq
import org.jetbrains.exposed.v1.core.not
import org.jetbrains.exposed.v1.core.or
import org.jetbrains.exposed.v1.core.SortOrder
import org.jetbrains.exposed.v1.dao.Entity
import org.jetbrains.exposed.v1.dao.EntityClass
import org.jetbrains.exposed.v1.javatime.CurrentTimestamp
import org.jetbrains.exposed.v1.javatime.timestamp
import java.io.File
import java.time.Instant
import java.time.temporal.ChronoUnit
import java.util.UUID
object WorldTable : UUIDTable("world") {
val name = varchar("Name", 512)
val version = integer("Version")
val type = enumeration<WorldType>("Type")
val owner = reference("Owner", SteamwarUserTable).nullable()
val team = reference("Team", TeamTable).nullable()
val lockState = varchar("LockState", 32).nullable()
val deleted = bool("Deleted").default(false)
val created = timestamp("Created").defaultExpression(CurrentTimestamp)
val lastUsed = timestamp("LastUsed").defaultExpression(CurrentTimestamp)
}
enum class WorldType {
BAU,
BUILDER,
TEAM,
ARENA,
TEMPLATE,
}
class SteamwarWorld(id: EntityID<UUID>) : Entity<UUID>(id) {
var name by WorldTable.name
private set
var version by WorldTable.version
private set
var type by WorldTable.type
private set
var owner by WorldTable.owner
private set
var team by WorldTable.team
private set
var lockState by WorldTable.lockState
private set
var deleted by WorldTable.deleted
private set
val created by WorldTable.created
var lastUsed by WorldTable.lastUsed
private set
val archived: Boolean
get() = !storageDirectory.exists() && archiveFile.exists()
val shouldArchive: Boolean
get() = !archived && (deleted || lastUsed.plus(7, ChronoUnit.DAYS).isBefore(Instant.now()))
val uuid: UUID
get() = id.value
val storageDirectory: File
get() = File(WORLD_STORAGE, id.value.toString())
private val archiveFile: File
get() = File(ARCHIVE_WORLD_STORAGE, "${id.value}.zip")
@JvmOverloads
fun setupAndGetStoragePath(prototype: File? = null): String {
if (archived) {
loadFromArchive()
}
val needsInitialization = !storageDirectory.exists() || storageDirectory.list()?.isEmpty() == true
if (needsInitialization) {
if (prototype != null && prototype.exists()) {
prototype.copyRecursively(storageDirectory, overwrite = true)
} else {
storageDirectory.mkdirs()
}
}
useDb {
lastUsed = Instant.now()
}
return storageDirectory.path
}
fun markDeleted() = useDb {
deleted = true
}
fun rename(newName: String) = useDb {
name = newName
}
fun archiveIfNeeded(): Boolean {
if (!shouldArchive) return false
File(ARCHIVE_WORLD_STORAGE).mkdirs()
return archiveWorld() == 0
}
fun changeVersion(newVersion: Int) = useDb {
version = newVersion
}
fun changeLockState(newLockState: String?) = useDb {
lockState = newLockState
}
private fun archiveWorld() = ProcessBuilder("zip", "-u9oymrqq", "$ARCHIVE_WORLD_STORAGE/${id.value}.zip", id.value.toString())
.directory(File(WORLD_STORAGE))
.inheritIO()
.start()
.waitFor()
private fun loadFromArchive() = ProcessBuilder("unzip", "-qq", "-o", "$ARCHIVE_WORLD_STORAGE/${id.value}.zip", "-d", WORLD_STORAGE)
.inheritIO()
.start()
.waitFor()
companion object : EntityClass<UUID, SteamwarWorld>(WorldTable) {
const val WORLD_STORAGE = "/worlds/storage"
const val ARCHIVE_WORLD_STORAGE = "/mnt/storage/worlds/storage"
const val DEFAULT_BAU_WORLD_LIMIT = 3
const val EXTRA_BAU_WORLD_LIMIT = 10
const val TEAM_WORLD_LIMIT = 2
@JvmStatic
fun getWorld(uuid: UUID) = useDb {
findById(uuid)
}
@JvmStatic
fun getBauWorld(user: SteamwarUser, version: Int) = useDb {
find {
(WorldTable.owner eq user.id) and
(WorldTable.version eq version) and
(WorldTable.type eq WorldType.BAU) and
not(WorldTable.deleted)
}.firstOrNull()
}
@JvmStatic
fun getBauWorld(user: SteamwarUser, name: String, version: Int) = useDb {
find {
(WorldTable.owner eq user.id) and
(WorldTable.name eq name) and
(WorldTable.version eq version) and
(WorldTable.type eq WorldType.BAU) and
not(WorldTable.deleted)
}.firstOrNull()
}
@JvmStatic
fun getBauWorlds(user: SteamwarUser) = useDb {
find {
(WorldTable.owner eq user.id) and
(WorldTable.type eq WorldType.BAU) and
not(WorldTable.deleted)
}.orderBy(WorldTable.created to SortOrder.ASC).toList()
}
@JvmStatic
fun countBauWorlds(user: SteamwarUser) = useDb {
find {
(WorldTable.owner eq user.id) and
(WorldTable.type eq WorldType.BAU) and
not(WorldTable.deleted)
}.count()
}
@JvmStatic
@JvmOverloads
fun getOrCreateBauWorld(user: SteamwarUser, version: Int, prototype: File? = null): SteamwarWorld =
getBauWorld(user, version) ?: createWorld(user, user.userName, version, WorldType.BAU, prototype)
@JvmStatic
@JvmOverloads
fun getOrCreateBauWorld(user: SteamwarUser, name: String, version: Int, prototype: File? = null): SteamwarWorld =
getBauWorld(user, name, version) ?: createWorld(user, name, version, WorldType.BAU, prototype)
@JvmStatic
fun getBuilderWorld(name: String, version: Int) = useDb {
find {
(WorldTable.name eq name) and
(WorldTable.version eq version) and
(WorldTable.type eq WorldType.BUILDER) and
not(WorldTable.deleted)
}.firstOrNull()
}
@JvmStatic
fun getBuilderWorlds(version: Int) = useDb {
find {
(WorldTable.version eq version) and
(WorldTable.type eq WorldType.BUILDER) and
not(WorldTable.deleted)
}.toList()
}
@JvmStatic
@JvmOverloads
fun getOrCreateBuilderWorld(name: String, version: Int, prototype: File? = null): SteamwarWorld =
getBuilderWorld(name, version) ?: createWorld(null, name, version, WorldType.BUILDER, prototype)
@JvmStatic
fun getTeamWorld(team: Team, name: String, version: Int) = useDb {
find {
(WorldTable.team eq team.id) and
(WorldTable.name eq name) and
(WorldTable.version eq version) and
(WorldTable.type eq WorldType.TEAM) and
not(WorldTable.deleted)
}.firstOrNull()
}
@JvmStatic
fun getTeamWorlds(team: Team) = useDb {
find {
(WorldTable.team eq team.id) and
(WorldTable.type eq WorldType.TEAM) and
not(WorldTable.deleted)
}.orderBy(WorldTable.created to SortOrder.ASC).toList()
}
@JvmStatic
@JvmOverloads
fun getOrCreateTeamWorld(team: Team, name: String, version: Int, prototype: File? = null): SteamwarWorld =
getTeamWorld(team, name, version) ?: createWorld(null, name, version, WorldType.TEAM, prototype, team)
@JvmStatic
fun countTeamWorlds(team: Team) = useDb {
find {
(WorldTable.team eq team.id) and
(WorldTable.type eq WorldType.TEAM) and
not(WorldTable.deleted)
}.count()
}
@JvmStatic
fun getArenaWorld(mode: String, map: String, version: Int) = useDb {
find {
(WorldTable.name eq arenaWorldName(mode, map)) and
(WorldTable.version eq version) and
(WorldTable.type eq WorldType.ARENA) and
not(WorldTable.deleted)
}.firstOrNull()
}
@JvmStatic
@JvmOverloads
fun getOrCreateArenaWorld(mode: String, map: String, version: Int, prototype: File? = null): SteamwarWorld =
getArenaWorld(mode, map, version) ?: createWorld(null, arenaWorldName(mode, map), version, WorldType.ARENA, prototype)
@JvmStatic
fun getTemplateWorld(name: String, version: Int) = useDb {
find {
(WorldTable.name eq name) and
(WorldTable.version eq version) and
(WorldTable.type eq WorldType.TEMPLATE) and
not(WorldTable.deleted)
}.firstOrNull()
}
@JvmStatic
fun getTemplateWorlds() = useDb {
find {
(WorldTable.type eq WorldType.TEMPLATE) and
not(WorldTable.deleted)
}.orderBy(WorldTable.name to SortOrder.ASC).toList()
}
@JvmStatic
fun getTemplateWorlds(version: Int) = useDb {
find {
(WorldTable.version eq version) and
(WorldTable.type eq WorldType.TEMPLATE) and
not(WorldTable.deleted)
}.orderBy(WorldTable.name to SortOrder.ASC).toList()
}
@JvmStatic
@JvmOverloads
fun getOrCreateTemplateWorld(name: String, version: Int, prototype: File? = null): SteamwarWorld =
getTemplateWorld(name, version) ?: createWorld(null, name, version, WorldType.TEMPLATE, prototype)
@JvmStatic
fun getAllActiveWorlds() = useDb {
find { not(WorldTable.deleted) }.toList()
}
@JvmStatic
fun getWorldsToArchive() = useDb {
find {
not(WorldTable.deleted) or (WorldTable.deleted eq true)
}.toList().filter { it.shouldArchive }
}
private fun arenaWorldName(mode: String, map: String) = "$mode/$map"
@JvmStatic
@JvmOverloads
fun createWorld(user: SteamwarUser?, name: String, version: Int, type: WorldType, prototype: File? = null, team: Team? = null): SteamwarWorld {
val world = useDb { new {
this.name = name
this.version = version
this.type = type
this.owner = user?.id
this.team = team?.id
} }
world.setupAndGetStoragePath(prototype)
return world
}
}
}
@@ -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;
@@ -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
@@ -31,16 +31,17 @@ import de.steamwar.linkage.Linked;
import net.md_5.bungee.api.ChatMessageType;
import org.bukkit.GameMode;
import org.bukkit.Material;
import org.bukkit.Tag;
import org.bukkit.block.Block;
import org.bukkit.block.data.type.Dispenser;
import org.bukkit.block.data.type.DriedGhast;
import org.bukkit.entity.Player;
import org.bukkit.entity.TNTPrimed;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.*;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockDispenseEvent;
import org.bukkit.event.block.BlockFromToEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.EntityExplodeEvent;
import org.bukkit.event.entity.FoodLevelChangeEvent;
import org.bukkit.event.entity.PlayerDeathEvent;
@@ -257,16 +258,4 @@ public class Permanent implements Listener {
event.setCancelled(true);
FightSystem.getMessage().sendPrefixless("NO_BLOCK_BREAK", event.getPlayer(), ChatMessageType.ACTION_BAR);
}
/**
* Prevents Dried Ghast from spawning and plants from growing
*/
@EventHandler
public void onBlockGrow(BlockGrowEvent event) {
var type = event.getBlock().getType();
if (event.getBlock().getBlockData() instanceof DriedGhast || Tag.CROPS.isTagged(type) || Tag.SAPLINGS.isTagged(type)) {
event.setCancelled(true);
}
}
}
@@ -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"
}
+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() {
@@ -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
@@ -17,15 +17,18 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.experimental;
package de.steamwar.core;
import de.steamwar.command.SWCommand;
import de.steamwar.linkage.Linked;
import lombok.experimental.UtilityClass;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.network.chat.contents.PlainTextContents;
@Linked
public class ExperimentalCommand extends SWCommand {
import java.util.Optional;
public ExperimentalCommand() {
super("experimental", "experiment");
@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 {
@@ -1,240 +0,0 @@
package de.steamwar.cursor;
import de.steamwar.core.SWPlayer;
import de.steamwar.entity.REntity;
import de.steamwar.entity.REntityServer;
import de.steamwar.entity.RFallingBlockEntity;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Player;
import org.bukkit.event.block.Action;
import org.bukkit.util.Vector;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Predicate;
@Getter
public class Cursor implements SWPlayer.Component {
private final World WORLD = Bukkit.getWorlds().get(0);
private final REntityServer targetServer;
private final Player owner;
private final AtomicBoolean isRendering = new AtomicBoolean(false);
private RFallingBlockEntity cursorEntity;
private final REntityServer cursorServer;
private Location cursorLocation;
private REntity hitEntity;
@Setter
private Material cursorMaterial;
@Setter
private List<CursorMode> allowedCursorModes;
private final Material highlightMaterial;
private final ClickHandler onClick;
private final BiConsumer<Location, Optional<REntity>> onRender;
public Cursor(REntityServer targetServer, Player owner, Material highlightMaterial, Material cursorMaterial, List<CursorMode> allowedModes, ClickHandler onClick) {
this(targetServer, owner, highlightMaterial, cursorMaterial, allowedModes, onClick, (location, hitEntity) -> {
});
}
public Cursor(REntityServer targetServer, Player owner, Material highlightMaterial, Material cursorMaterial, List<CursorMode> allowedModes, ClickHandler onClick, BiConsumer<Location, Optional<REntity>> onRender) {
this.targetServer = targetServer;
this.owner = owner;
this.highlightMaterial = highlightMaterial;
this.cursorMaterial = cursorMaterial;
this.allowedCursorModes = allowedModes;
this.onClick = onClick;
this.onRender = onRender;
cursorServer = new REntityServer();
cursorServer.addPlayer(owner);
SWPlayer.of(owner).setComponent(this);
}
public void renderDeduplicated() {
if (!isRendering.getAndSet(true)) {
render();
isRendering.set(false);
}
}
private void render() {
RayTraceUtils.RRayTraceResult rayTraceResult = RayTraceUtils.traceREntity(owner, owner.getLocation(), targetServer.getEntities());
if (rayTraceResult == null) {
if (cursorEntity != null)
cursorEntity.die();
cursorEntity = null;
cursorLocation = null;
hitEntity = null;
onRender.accept(null, Optional.empty());
return;
}
REntity hitEntity = rayTraceResult.getHitEntity() == cursorEntity ? null : rayTraceResult.getHitEntity();
Material activeCursorMaterial = hitEntity == null ? cursorMaterial : highlightMaterial;
CursorMode activeCursorMode = allowedCursorModes.stream().filter((mode) -> mode.isActive.test(owner)).min(Comparator.comparingInt(a -> a.priority))
.orElse(CursorMode.BLOCK_ALIGNED);
Location activeCursorLocation = hitEntity == null ? activeCursorMode.positionTransform.apply(owner, rayTraceResult).toLocation(WORLD)
: new Vector(hitEntity.getX(), hitEntity.getY(), hitEntity.getZ()).toLocation(WORLD);
cursorLocation = activeCursorLocation;
this.hitEntity = hitEntity;
if (cursorEntity == null) {
cursorEntity = new RFallingBlockEntity(cursorServer, activeCursorLocation, activeCursorMaterial);
cursorEntity.setNoGravity(true);
} else if (cursorEntity.getMaterial() == activeCursorMaterial) {
cursorEntity.move(activeCursorLocation);
} else {
cursorEntity.die();
cursorEntity = new RFallingBlockEntity(cursorServer, activeCursorLocation, activeCursorMaterial);
cursorEntity.setNoGravity(true);
if (activeCursorMaterial == highlightMaterial) {
cursorEntity.setGlowing(true);
}
}
onRender.accept(cursorLocation, Optional.ofNullable(hitEntity));
}
protected void handlePlayerClick(Action clickAction) {
renderDeduplicated();
onClick.onClick(this.cursorLocation, Optional.ofNullable(this.hitEntity), clickAction);
}
@Override
public void onUnmount(SWPlayer player) {
cursorServer.close();
}
@AllArgsConstructor
public enum CursorMode {
FREE(1, (player, rayTraceResult) -> {
Vector pos = rayTraceResult.getHitPosition();
BlockFace face = rayTraceResult.getHitBlockFace();
if (face != null) {
switch (face) {
case DOWN:
pos.setY(pos.getY() - 0.98);
break;
case EAST:
pos.setX(pos.getX() + 0.49);
break;
case WEST:
pos.setX(pos.getX() - 0.49);
break;
case NORTH:
pos.setZ(pos.getZ() - 0.49);
break;
case SOUTH:
pos.setZ(pos.getZ() + 0.49);
break;
default:
break;
}
if (face.getModY() == 0 && player.isSneaking()) {
pos.setY(pos.getY() - 0.49);
}
}
return pos;
}, Player::isSneaking),
SURFACE_ALIGNED(2, (player, rayTraceResult) -> {
Vector hitPosition = rayTraceResult.getHitPosition().clone();
Vector pos = blockAlignedPosition(rayTraceResult);
BlockFace face = rayTraceResult.getHitBlockFace();
if (face != null && face != BlockFace.SELF) {
switch (face) {
case UP:
pos.setY(hitPosition.getY());
break;
case DOWN:
pos.setY(hitPosition.getY() + face.getModY());
break;
case EAST:
case WEST:
pos.setX(hitPosition.getX() + face.getModX() * 0.5);
break;
case NORTH:
case SOUTH:
pos.setZ(hitPosition.getZ() + face.getModZ() * 0.5);
break;
default:
break;
}
}
return pos;
}, (player) -> true),
BLOCK_ALIGNED(0, (player, rayTraceResult) -> blockAlignedPosition(rayTraceResult), (player) -> true);
private final int priority;
private final BiFunction<Player, RayTraceUtils.RRayTraceResult, Vector> positionTransform;
private final Predicate<Player> isActive;
private static Vector blockAlignedPosition(RayTraceUtils.RRayTraceResult rayTraceResult) {
Vector pos = rayTraceResult.getHitPosition();
BlockFace face = rayTraceResult.getHitBlockFace();
if (face != null) {
switch (face) {
case DOWN:
pos.setY(pos.getY() - 0.98);
break;
case EAST:
pos.setX(pos.getX() + 0.49);
break;
case WEST:
pos.setX(pos.getX() - 0.49);
break;
case NORTH:
pos.setZ(pos.getZ() - 0.49);
break;
case SOUTH:
pos.setZ(pos.getZ() + 0.49);
break;
default:
break;
}
}
pos.setX(pos.getBlockX() + 0.5);
if (pos.getY() - pos.getBlockY() != 0 && face == BlockFace.UP) {
pos.setY(pos.getBlockY() + 1.0);
} else {
pos.setY(pos.getBlockY());
}
pos.setZ(pos.getBlockZ() + 0.5);
return pos;
}
}
@FunctionalInterface
public interface ClickHandler {
void onClick(Location location, Optional<REntity> hitEntity, Action action);
}
}
@@ -1,56 +0,0 @@
package de.steamwar.cursor;
import com.comphenix.tinyprotocol.TinyProtocol;
import de.steamwar.core.Core;
import de.steamwar.core.SWPlayer;
import de.steamwar.linkage.Linked;
import lombok.Getter;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerInteractEvent;
import java.util.Optional;
@Linked
public class CursorListener implements Listener {
@Getter
private static CursorListener instance;
public CursorListener() {
if (instance == null) {
instance = this;
}
TinyProtocol.instance.addFilter(ServerboundMovePlayerPacket.Pos.class, this::updateCursorFromPacket);
TinyProtocol.instance.addFilter(ServerboundMovePlayerPacket.Rot.class, this::updateCursorFromPacket);
TinyProtocol.instance.addFilter(ServerboundMovePlayerPacket.PosRot.class, this::updateCursorFromPacket);
Bukkit.getScheduler().runTaskTimer(Core.getInstance(), () -> {
SWPlayer.allWithSingleComponent(Cursor.class)
.map(SWPlayer.SWPlayerWithComponent::getComponent)
.forEach(Cursor::renderDeduplicated);
}, 1, 1);
}
public Packet<?> updateCursorFromPacket(Player player, Packet<?> packet) {
SWPlayer swPlayer = SWPlayer.of(player);
Optional<Cursor> activeCursor = swPlayer.getComponent(Cursor.class);
activeCursor.ifPresent(Cursor::renderDeduplicated);
return packet;
}
@EventHandler
public void onPlayerInteract(PlayerInteractEvent event) {
SWPlayer.of(event.getPlayer()).getComponent(Cursor.class).ifPresent(cursor -> {
event.setCancelled(true);
cursor.handlePlayerClick(event.getAction());
});
}
}
@@ -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);
}
}
}
@@ -20,14 +20,54 @@
package de.steamwar.providers;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.WorldType;
import org.bukkit.Bukkit;
import java.util.UUID;
public class BauServerInfo {
private static Integer bauOwner = null;
private static Integer bauTeam = null;
private static UUID bauWorld = null;
private static WorldType bauType = null;
static {
String ownerProperty = System.getProperty("bauOwner");
if (ownerProperty != null && !ownerProperty.isBlank()) {
try {
bauOwner = Integer.parseInt(ownerProperty);
} catch (NumberFormatException ignored) {
}
}
String teamProperty = System.getProperty("bauTeam");
if (teamProperty != null && !teamProperty.isBlank()) {
try {
bauTeam = Integer.parseInt(teamProperty);
} catch (NumberFormatException ignored) {
}
}
String worldProperty = System.getProperty("bauWorld");
if (worldProperty != null && !worldProperty.isBlank()) {
try {
bauWorld = UUID.fromString(worldProperty);
} catch (IllegalArgumentException ignored) {
}
}
String typeProperty = System.getProperty("bauType");
if (typeProperty != null && !typeProperty.isBlank()) {
try {
bauType = WorldType.valueOf(typeProperty);
} catch (IllegalArgumentException ignored) {
}
}
try {
bauOwner = Integer.parseInt(Bukkit.getWorlds().get(0).getName());
if (bauOwner == null) {
bauOwner = Integer.parseInt(Bukkit.getWorlds().get(0).getName());
}
} catch (NumberFormatException ignored) {
}
}
@@ -37,11 +77,23 @@ public class BauServerInfo {
}
public static boolean isBauServer() {
return bauOwner != null;
return bauOwner != null || bauWorld != null || bauTeam != null;
}
public static SteamwarUser getOwnerUser() {
if (bauOwner == null) return null;
return SteamwarUser.byId(bauOwner);
}
public static UUID getWorldId() {
return bauWorld;
}
public static Integer getTeamId() {
return bauTeam;
}
public static WorldType getWorldType() {
return bauType;
}
}
@@ -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;
@@ -151,7 +152,7 @@ public class TechHider {
ClientboundSetHeldSlotPacket.class, // 7.1.104 Set Held Item (Player owning the channel)
ClientboundSetObjectivePacket.class, // 7.1.105 Update Objectives
ClientboundSetPlayerInventoryPacket.class, // 7.1.107 Set Player Inventory Slot (Player owning the channel)
// ClientboundSetPlayerTeamPacket.class, // 7.1.108 Update Teams
ClientboundSetPlayerTeamPacket.class, // 7.1.108 Update Teams
ClientboundSetScorePacket.class, // 7.1.109 Update Score
ClientboundSetSimulationDistancePacket.class, // 7.1.110 Set Simulation Distance
ClientboundSetSubtitleTextPacket.class, // 7.1.111 Set Subtitle Text
@@ -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);

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