Compare commits
30
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a01da8a677 | ||
|
|
77a6d74739 | ||
|
|
d12bb8041f | ||
|
|
44cbaad197 | ||
|
|
faf2fb3afb | ||
|
|
7461aca13f | ||
|
|
f381805cad | ||
|
|
2fa022eb00 | ||
|
|
44900e584b | ||
|
|
a2f4dc1979 | ||
|
|
0237f956ff | ||
|
|
e222df8e87 | ||
|
|
bbab7e222c | ||
|
|
41c7e41a0e | ||
|
|
5551ef68e1 | ||
|
|
ca6e83de4d | ||
|
|
4d6ac75c73 | ||
|
|
1317ae0bb4 | ||
|
|
d538c59a79 | ||
|
|
6a223a30c7 | ||
|
|
2e1c79b4e3 | ||
|
|
bb143d7993 | ||
|
|
322d2567c0 | ||
|
|
8d7a4cce87 | ||
|
|
832194a1e9 | ||
|
|
0321e0bceb | ||
|
|
63854605b6 | ||
|
|
5c4602188e | ||
|
|
163e120f00 | ||
|
|
ce6e1d3e9f |
No files matched your search
@@ -7,6 +7,8 @@ indent_style = space
|
||||
insert_final_newline = true
|
||||
max_line_length = 100
|
||||
tab_width = 4
|
||||
end_of_line = crlf
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
# intellij specific configuration
|
||||
ij_continuation_indent_size = 8
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import kotlinx.serialization.json.*
|
||||
import java.util.jar.JarFile
|
||||
import java.util.zip.ZipEntry
|
||||
|
||||
plugins {
|
||||
`java-library`
|
||||
@@ -27,26 +28,35 @@ fun parseFabricModJson(jarFile: File, project: Project): Set<DependencyIdentifie
|
||||
val nestedJars = mutableSetOf<DependencyIdentifier>()
|
||||
try {
|
||||
JarFile(jarFile).use { jar ->
|
||||
val fabricModJsonEntry = jar.getEntry("fabric.mod.json") ?: return@use
|
||||
jar.getInputStream(fabricModJsonEntry).bufferedReader().use { reader ->
|
||||
val jsonObject = Json.parseToJsonElement(reader.readText()).jsonObject
|
||||
val jars = jsonObject["jars"]?.jsonArray ?: return@use
|
||||
jars.forEach { jarEntry ->
|
||||
val filePath = jarEntry.jsonObject["file"]?.jsonPrimitive?.content ?: return@forEach
|
||||
project.logger.debug("Found nested JAR in fabric.mod.json: {} in {}", filePath, jarFile)
|
||||
val jarName = filePath.substringAfterLast("/")
|
||||
val parts = jarName.removeSuffix(".jar").split("-")
|
||||
if (parts.size >= 2) {
|
||||
val inferredName = parts.dropLast(1).joinToString("-")
|
||||
val inferredVersion = parts.last()
|
||||
nestedJars.add(DependencyIdentifier("", inferredName, inferredVersion))
|
||||
project.logger.debug("Inferred nested dependency from fabric.mod.json: :$inferredName:$inferredVersion")
|
||||
val fabricModJsonEntry: ZipEntry? = jar.getEntry("fabric.mod.json")
|
||||
if (fabricModJsonEntry != null) {
|
||||
jar.getInputStream(fabricModJsonEntry).bufferedReader().use { reader ->
|
||||
val jsonContent = reader.readText()
|
||||
val jsonElement = Json.parseToJsonElement(jsonContent)
|
||||
val jsonObject = jsonElement.jsonObject
|
||||
val jarsArray = jsonObject["jars"]?.jsonArray ?: return@use
|
||||
jarsArray.forEach { jarEntry: JsonElement ->
|
||||
val filePath =
|
||||
jarEntry.jsonObject["file"]?.jsonPrimitive?.content ?: return@forEach
|
||||
project.logger.debug(
|
||||
"Found nested JAR in fabric.mod.json: {} in {}",
|
||||
filePath,
|
||||
jarFile
|
||||
)
|
||||
val jarName = filePath.substringAfterLast("/")
|
||||
val parts = jarName.removeSuffix(".jar").split("-")
|
||||
if (parts.size >= 2) {
|
||||
val inferredName = parts.dropLast(1).joinToString("-")
|
||||
val inferredVersion = parts.last()
|
||||
nestedJars.add(DependencyIdentifier("", inferredName, inferredVersion))
|
||||
project.logger.debug("Inferred nested dependency from fabric.mod.json: :$inferredName:$inferredVersion")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
project.logger.error("Failed to parse fabric.mod.json in {}: {}", jarFile, e.message)
|
||||
project.logger.error("Failed to parse fabric.mod.json in $jarFile: ${e.message}")
|
||||
}
|
||||
return nestedJars
|
||||
}
|
||||
@@ -56,41 +66,50 @@ fun extractEmbeddedJars(jarFile: File, project: Project): Set<DependencyIdentifi
|
||||
embeddedJars.addAll(parseFabricModJson(jarFile, project)) // Retain existing logic for top-level fabric.mod.json parsing
|
||||
try {
|
||||
JarFile(jarFile).use { jar ->
|
||||
for (entry in jar.entries()) {
|
||||
val entries = jar.entries()
|
||||
while (entries.hasMoreElements()) {
|
||||
val entry = entries.nextElement()
|
||||
val entryName = entry.name
|
||||
if (!entryName.endsWith(".jar") || !entryName.startsWith("META-INF/jarjar/")
|
||||
&& !entryName.startsWith("libs/") && !entryName.startsWith("META-INF/jars/")
|
||||
) continue
|
||||
project.logger.debug("Found embedded JAR: {} in {}", entryName, jarFile)
|
||||
val tempFile = File.createTempFile("embedded-", ".jar").also(File::deleteOnExit)
|
||||
jar.getInputStream(entry).use { input ->
|
||||
tempFile.outputStream().use { output ->
|
||||
input.copyTo(output)
|
||||
if (entryName.endsWith(".jar") && (entryName.startsWith("META-INF/jarjar/") || entryName.startsWith(
|
||||
"libs/"
|
||||
) || entryName.startsWith("META-INF/jars/"))
|
||||
) {
|
||||
project.logger.debug("Found embedded JAR: {} in {}", entryName, jarFile)
|
||||
val tempFile = File.createTempFile("embedded-", ".jar")
|
||||
tempFile.deleteOnExit()
|
||||
jar.getInputStream(entry).use { input ->
|
||||
tempFile.outputStream().use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
JarFile(tempFile).use { embeddedJar ->
|
||||
val fabricModJsonEntry = embeddedJar.getEntry("fabric.mod.json") ?: run {
|
||||
project.logger.error("No fabric.mod.json found in embedded JAR: {}. Skipping.", entryName)
|
||||
return@use
|
||||
}
|
||||
embeddedJar.getInputStream(fabricModJsonEntry).bufferedReader().use { reader ->
|
||||
val jsonObject = Json.parseToJsonElement(reader.readText()).jsonObject
|
||||
JarFile(tempFile).use { embeddedJar ->
|
||||
val fabricModJsonEntry: ZipEntry? = embeddedJar.getEntry("fabric.mod.json")
|
||||
if (fabricModJsonEntry != null) {
|
||||
embeddedJar.getInputStream(fabricModJsonEntry).bufferedReader().use { reader ->
|
||||
val jsonContent = reader.readText()
|
||||
val jsonElement = Json.parseToJsonElement(jsonContent)
|
||||
val jsonObject = jsonElement.jsonObject
|
||||
|
||||
// Extract id and parse into group
|
||||
val id = jsonObject["id"]?.jsonPrimitive?.content
|
||||
?: error("Missing 'id' in fabric.mod.json of embedded JAR: $entryName")
|
||||
// Extract id and parse into group
|
||||
val id = jsonObject["id"]?.jsonPrimitive?.content
|
||||
?: throw IllegalStateException("Missing 'id' in fabric.mod.json of embedded JAR: $entryName")
|
||||
val idWithDots = id.replace("_", ".")
|
||||
val lastDotIndex = idWithDots.lastIndexOf(".")
|
||||
val group = if (lastDotIndex != -1) idWithDots.take(lastDotIndex) else ""
|
||||
|
||||
val group = id.replace('_', '.').dropLastWhile { it != '.' }.dropLast(1)
|
||||
// Extract name and version from the JAR filename
|
||||
val jarName = entryName.substringAfterLast("/").removeSuffix(".jar")
|
||||
val (name, version) = parseNameAndVersionFromJarName(jarName)
|
||||
?: throw IllegalStateException("Could not parse name and version from JAR filename: $jarName")
|
||||
|
||||
// Extract name and version from the JAR filename
|
||||
val jarName = entryName.substringAfterLast("/").removeSuffix(".jar")
|
||||
val (name, version) = parseNameAndVersionFromJarName(jarName)
|
||||
?: error("Could not parse name and version from JAR filename: $jarName")
|
||||
|
||||
// Create DependencyIdentifier (classifier is empty for now)
|
||||
val depId = DependencyIdentifier(group, name, version)
|
||||
embeddedJars.add(depId)
|
||||
project.logger.debug("Identified embedded dependency from fabric.mod.json and filename: {}", depId)
|
||||
// Create DependencyIdentifier (classifier is empty for now)
|
||||
val depId = DependencyIdentifier(group, name, version)
|
||||
embeddedJars.add(depId)
|
||||
project.logger.debug("Identified embedded dependency from fabric.mod.json and filename: {}", depId)
|
||||
}
|
||||
} else {
|
||||
project.logger.error("No fabric.mod.json found in embedded JAR: {}. Skipping.", entryName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,13 +126,15 @@ fun parseNameAndVersionFromJarName(jarName: String): Pair<String, String>? {
|
||||
val versionStartMatch = versionStartRegex.find(jarName) ?: return null
|
||||
val versionStartIndex = versionStartMatch.range.first
|
||||
|
||||
if (versionStartIndex == 0 || versionStartIndex + 1 >= jarName.length) return null
|
||||
|
||||
// Split the JAR name into name and version at the version start index
|
||||
val name = jarName.take(versionStartIndex)
|
||||
val version = jarName.drop(versionStartIndex + 1) // Skip the hyphen
|
||||
val version = jarName.substring(versionStartIndex + 1) // Skip the hyphen
|
||||
|
||||
return Pair(name, version)
|
||||
return if (name.isNotEmpty() && version.isNotEmpty()) {
|
||||
Pair(name, version)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun shouldExcludeDependency(
|
||||
@@ -166,12 +187,15 @@ fun isJijTarget(
|
||||
project: Project
|
||||
): Boolean {
|
||||
val classifier = dependency.moduleArtifacts.firstOrNull()?.classifier ?: ""
|
||||
project.logger.debug("Checking JIJ target: {}", DependencyIdentifier(
|
||||
dependency.moduleGroup,
|
||||
dependency.moduleName,
|
||||
dependency.moduleVersion,
|
||||
classifier
|
||||
))
|
||||
project.logger.debug(
|
||||
"Checking JIJ target: {}",
|
||||
DependencyIdentifier(
|
||||
dependency.moduleGroup,
|
||||
dependency.moduleName,
|
||||
dependency.moduleVersion,
|
||||
classifier
|
||||
)
|
||||
)
|
||||
return !shouldExcludeDependency(
|
||||
dependency,
|
||||
allEmbeddedDependencies,
|
||||
@@ -189,14 +213,14 @@ fun includeDependencyWithExclusions(
|
||||
project.logger.debug("Including external dependency as JIJ with dynamic exclusions: {}", depId)
|
||||
project.dependencies {
|
||||
// Include dependency with classifier (if present)
|
||||
"include"(depId.toString()) {
|
||||
"include"("${depId.group}:${depId.name}:${depId.version}${if (depId.classifier.isNotEmpty()) ":${depId.classifier}" else ""}") {
|
||||
allEmbeddedDependencies.forEach { embeddedDep ->
|
||||
if (embeddedDep.group.isNotEmpty()) {
|
||||
exclude(group = embeddedDep.group, module = embeddedDep.name)
|
||||
} else {
|
||||
exclude(module = embeddedDep.name)
|
||||
}
|
||||
project.logger.debug("Excluding embedded transitive dependency: {}", embeddedDep)
|
||||
project.logger.debug("Excluding embedded transitive dependency: ${embeddedDep.name}:${embeddedDep.version}${if (embeddedDep.classifier.isNotEmpty()) ":${embeddedDep.classifier}" else ""}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -212,52 +236,59 @@ fun processDependencies(
|
||||
) {
|
||||
dependencies.forEach { dep: ResolvedDependency ->
|
||||
val classifier = dep.moduleArtifacts.firstOrNull()?.classifier ?: ""
|
||||
val depKey = "${dep.moduleGroup}:${dep.moduleName}:${dep.moduleVersion}${if (classifier.isNotEmpty()) ":$classifier" else ""}"
|
||||
val depId = DependencyIdentifier(dep.moduleGroup, dep.moduleName, dep.moduleVersion, classifier)
|
||||
val depKey = depId.toString()
|
||||
if (!processed.contains(depKey)) {
|
||||
processed.add(depKey)
|
||||
project.logger.debug("Resolved dependency: {}", depId)
|
||||
|
||||
if (!processed.add(depKey)) return
|
||||
|
||||
project.logger.debug("Resolved dependency: {}", depKey)
|
||||
|
||||
val jarFile = dep.moduleArtifacts.firstOrNull()?.file
|
||||
if (jarFile != null && jarFile.exists()) {
|
||||
val embeddedJars = extractEmbeddedJars(jarFile, project)
|
||||
embeddedJars.forEach { embeddedDep ->
|
||||
allEmbeddedDependencies[embeddedDep] = jarFile
|
||||
}
|
||||
}
|
||||
|
||||
if (isJijTarget(dep, allEmbeddedDependencies.keys, actuallyIncludedDependencies, project)) {
|
||||
project.logger.debug("Processing JIJ dependency: {}", depId)
|
||||
val isProjectDependency = project.rootProject.allprojects.any { subproject ->
|
||||
subproject.name == dep.moduleName && (subproject.group == dep.moduleGroup || dep.moduleGroup.isEmpty())
|
||||
}
|
||||
if (isProjectDependency) {
|
||||
val projectPath = project.rootProject.allprojects.find { subproject ->
|
||||
subproject.name == dep.moduleName && (subproject.group == dep.moduleGroup || dep.moduleGroup.isEmpty())
|
||||
}?.path ?: error("Project dependency not found: ${dep.moduleName}")
|
||||
project.logger.debug("Including project dependency as JIJ: {}", projectPath)
|
||||
project.dependencies {
|
||||
"include"(project.project(projectPath))
|
||||
val jarFile = dep.moduleArtifacts.firstOrNull()?.file
|
||||
if (jarFile != null && jarFile.exists()) {
|
||||
val embeddedJars = extractEmbeddedJars(jarFile, project)
|
||||
embeddedJars.forEach { embeddedDep ->
|
||||
allEmbeddedDependencies[embeddedDep] = jarFile
|
||||
}
|
||||
actuallyIncludedDependencies.add(depId)
|
||||
} else {
|
||||
includeDependencyWithExclusions(
|
||||
depId,
|
||||
project,
|
||||
actuallyIncludedDependencies,
|
||||
allEmbeddedDependencies.keys
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
processDependencies(
|
||||
dep.children,
|
||||
processed,
|
||||
allEmbeddedDependencies,
|
||||
actuallyIncludedDependencies,
|
||||
project
|
||||
)
|
||||
if (isJijTarget(
|
||||
dep,
|
||||
allEmbeddedDependencies.keys,
|
||||
actuallyIncludedDependencies,
|
||||
project
|
||||
)
|
||||
) {
|
||||
project.logger.debug("Processing JIJ dependency: {}", depId)
|
||||
val isProjectDependency = project.rootProject.allprojects.any { subproject ->
|
||||
subproject.name == dep.moduleName && (subproject.group == dep.moduleGroup || dep.moduleGroup.isEmpty())
|
||||
}
|
||||
if (isProjectDependency) {
|
||||
val projectPath = project.rootProject.allprojects.find { subproject ->
|
||||
subproject.name == dep.moduleName && (subproject.group == dep.moduleGroup || dep.moduleGroup.isEmpty())
|
||||
}?.path
|
||||
?: throw IllegalStateException("Project dependency not found: ${dep.moduleName}")
|
||||
project.logger.debug("Including project dependency as JIJ: $projectPath")
|
||||
project.dependencies {
|
||||
"include"(project.project(projectPath))
|
||||
}
|
||||
actuallyIncludedDependencies.add(depId)
|
||||
} else {
|
||||
includeDependencyWithExclusions(
|
||||
depId,
|
||||
project,
|
||||
actuallyIncludedDependencies,
|
||||
allEmbeddedDependencies.keys
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
processDependencies(
|
||||
dep.children,
|
||||
processed,
|
||||
allEmbeddedDependencies,
|
||||
actuallyIncludedDependencies,
|
||||
project
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,16 +304,22 @@ project.afterEvaluate {
|
||||
dependencies.forEach { dep ->
|
||||
val classifier = dep.moduleArtifacts.firstOrNull()?.classifier ?: ""
|
||||
val depKey = "${dep.moduleGroup}:${dep.moduleName}:${dep.moduleVersion}${if (classifier.isNotEmpty()) ":$classifier" else ""}"
|
||||
if (!processed.add(depKey)) return@forEach
|
||||
val jarFile = dep.moduleArtifacts.firstOrNull()?.file
|
||||
if (jarFile != null && jarFile.exists()) {
|
||||
val embeddedJars = extractEmbeddedJars(jarFile, project)
|
||||
embeddedJars.forEach { embeddedDep ->
|
||||
allEmbeddedDependencies[embeddedDep] = jarFile
|
||||
project.logger.debug("Collected embedded dependency: {} from {}", embeddedDep, jarFile)
|
||||
if (!processed.contains(depKey)) {
|
||||
processed.add(depKey)
|
||||
val jarFile = dep.moduleArtifacts.firstOrNull()?.file
|
||||
if (jarFile != null && jarFile.exists()) {
|
||||
val embeddedJars = extractEmbeddedJars(jarFile, project)
|
||||
embeddedJars.forEach { embeddedDep ->
|
||||
allEmbeddedDependencies[embeddedDep] = jarFile
|
||||
project.logger.debug(
|
||||
"Collected embedded dependency: {} from {}",
|
||||
embeddedDep,
|
||||
jarFile
|
||||
)
|
||||
}
|
||||
}
|
||||
collectAllEmbeddedDependenciesForAfterEvaluate(dep.children)
|
||||
}
|
||||
collectAllEmbeddedDependenciesForAfterEvaluate(dep.children)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,7 +339,8 @@ project.afterEvaluate {
|
||||
tasks.withType<Jar>().configureEach {
|
||||
doFirst {
|
||||
project.logger.debug("Resolving JIJ dependencies for JAR inclusion")
|
||||
val resolvedDependencies = jijDependencies.resolvedConfiguration.firstLevelModuleDependencies
|
||||
val resolvedDependencies =
|
||||
jijDependencies.resolvedConfiguration.firstLevelModuleDependencies
|
||||
val processed = mutableSetOf<String>()
|
||||
val allEmbeddedDependencies = mutableMapOf<DependencyIdentifier, File>()
|
||||
val actuallyIncludedDependencies = mutableSetOf<DependencyIdentifier>()
|
||||
@@ -310,20 +348,22 @@ tasks.withType<Jar>().configureEach {
|
||||
fun collectAllEmbeddedDependenciesForJar(dependencies: Set<ResolvedDependency>) {
|
||||
dependencies.forEach { dep ->
|
||||
val depKey = "${dep.moduleGroup}:${dep.moduleName}:${dep.moduleVersion}"
|
||||
if (!processed.add(depKey)) return@forEach
|
||||
val jarFile = dep.moduleArtifacts.firstOrNull()?.file
|
||||
if (jarFile != null && jarFile.exists()) {
|
||||
val embeddedJars = extractEmbeddedJars(jarFile, project)
|
||||
embeddedJars.forEach { embeddedDep ->
|
||||
allEmbeddedDependencies[embeddedDep] = jarFile
|
||||
project.logger.debug(
|
||||
"Collected embedded dependency for JAR: {} from {}",
|
||||
embeddedDep,
|
||||
jarFile
|
||||
)
|
||||
if (!processed.contains(depKey)) {
|
||||
processed.add(depKey)
|
||||
val jarFile = dep.moduleArtifacts.firstOrNull()?.file
|
||||
if (jarFile != null && jarFile.exists()) {
|
||||
val embeddedJars = extractEmbeddedJars(jarFile, project)
|
||||
embeddedJars.forEach { embeddedDep ->
|
||||
allEmbeddedDependencies[embeddedDep] = jarFile
|
||||
project.logger.debug(
|
||||
"Collected embedded dependency for JAR: {} from {}",
|
||||
embeddedDep,
|
||||
jarFile
|
||||
)
|
||||
}
|
||||
}
|
||||
collectAllEmbeddedDependenciesForJar(dep.children)
|
||||
}
|
||||
collectAllEmbeddedDependenciesForJar(dep.children)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,27 +376,30 @@ tasks.withType<Jar>().configureEach {
|
||||
dependencies.forEach { dep ->
|
||||
val depKey = "${dep.moduleGroup}:${dep.moduleName}:${dep.moduleVersion}"
|
||||
val depId = DependencyIdentifier(dep.moduleGroup, dep.moduleName, dep.moduleVersion)
|
||||
if (!processed.add(depKey)) return@forEach
|
||||
project.logger.debug("Resolved JIJ dependency for JAR: {}", depId)
|
||||
if (!processed.contains(depKey)) {
|
||||
processed.add(depKey)
|
||||
project.logger.debug("Resolved JIJ dependency for JAR: {}", depId)
|
||||
|
||||
val jarFile = dep.moduleArtifacts.firstOrNull()?.file
|
||||
if (jarFile != null && jarFile.exists()) {
|
||||
val embeddedJars = extractEmbeddedJars(jarFile, project)
|
||||
embeddedJars.forEach { embeddedDep ->
|
||||
allEmbeddedDependencies[embeddedDep] = jarFile
|
||||
val jarFile = dep.moduleArtifacts.firstOrNull()?.file
|
||||
if (jarFile != null && jarFile.exists()) {
|
||||
val embeddedJars = extractEmbeddedJars(jarFile, project)
|
||||
embeddedJars.forEach { embeddedDep ->
|
||||
allEmbeddedDependencies[embeddedDep] = jarFile
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isJijTarget(
|
||||
dep,
|
||||
allEmbeddedDependencies.keys,
|
||||
actuallyIncludedDependencies,
|
||||
project
|
||||
)) {
|
||||
project.logger.debug("Including JIJ dependency in JAR: {}", depId)
|
||||
actuallyIncludedDependencies.add(depId)
|
||||
if (isJijTarget(
|
||||
dep,
|
||||
allEmbeddedDependencies.keys,
|
||||
actuallyIncludedDependencies,
|
||||
project
|
||||
)
|
||||
) {
|
||||
project.logger.debug("Including JIJ dependency in JAR: {}", depId)
|
||||
actuallyIncludedDependencies.add(depId)
|
||||
}
|
||||
logDependencies(dep.children)
|
||||
}
|
||||
logDependencies(dep.children)
|
||||
}
|
||||
}
|
||||
logDependencies(resolvedDependencies)
|
||||
|
||||
@@ -60,7 +60,8 @@ object VersionUtil {
|
||||
.apply { waitFor() }
|
||||
.inputStream
|
||||
.use { stdout.writeBytes(it.readAllBytes()) }
|
||||
return stdout.toString().trim()
|
||||
val fullCommit = stdout.toString().trim()
|
||||
return fullCommit.take(minOf(fullCommit.length, 7))
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+22
-2
@@ -9,8 +9,11 @@ import ac.grim.grimac.platform.bukkit.entity.BukkitGrimEntity;
|
||||
import ac.grim.grimac.platform.bukkit.utils.anticheat.MultiLibUtil;
|
||||
import ac.grim.grimac.platform.bukkit.utils.convert.BukkitConversionUtils;
|
||||
import ac.grim.grimac.platform.bukkit.utils.reflection.PaperUtils;
|
||||
import ac.grim.grimac.utils.common.arguments.CommonGrimArguments;
|
||||
import ac.grim.grimac.utils.math.Location;
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.github.retrooper.packetevents.protocol.player.GameMode;
|
||||
import com.github.retrooper.packetevents.protocol.player.User;
|
||||
import com.github.retrooper.packetevents.util.Vector3d;
|
||||
import io.github.retrooper.packetevents.util.SpigotConversionUtil;
|
||||
import lombok.Getter;
|
||||
@@ -27,6 +30,7 @@ import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class BukkitPlatformPlayer extends BukkitGrimEntity implements PlatformPlayer {
|
||||
|
||||
private static final BukkitAudiences audiences = BukkitAudiences.create(GrimACBukkitLoaderPlugin.LOADER);
|
||||
|
||||
@Getter
|
||||
@@ -34,10 +38,18 @@ public class BukkitPlatformPlayer extends BukkitGrimEntity implements PlatformPl
|
||||
@Getter
|
||||
private final PlatformInventory inventory;
|
||||
|
||||
private final @Nullable User user;
|
||||
|
||||
public BukkitPlatformPlayer(Player bukkitPlayer) {
|
||||
super(bukkitPlayer);
|
||||
this.bukkitPlayer = bukkitPlayer;
|
||||
this.inventory = new BukkitPlatformInventory(bukkitPlayer);
|
||||
if (CommonGrimArguments.USE_CHAT_FAST_BYPASS.value()) {
|
||||
Object channel = PacketEvents.getAPI().getProtocolManager().getChannel(bukkitPlayer.getUniqueId());
|
||||
this.user = PacketEvents.getAPI().getProtocolManager().getUser(channel);
|
||||
} else {
|
||||
this.user = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -67,12 +79,20 @@ public class BukkitPlatformPlayer extends BukkitGrimEntity implements PlatformPl
|
||||
|
||||
@Override
|
||||
public void sendMessage(String message) {
|
||||
bukkitPlayer.sendMessage(message);
|
||||
if (CommonGrimArguments.USE_CHAT_FAST_BYPASS.value() && user != null) {
|
||||
user.sendMessage(message);
|
||||
} else {
|
||||
bukkitPlayer.sendMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(Component message) {
|
||||
audiences.player(bukkitPlayer).sendMessage(message);
|
||||
if (CommonGrimArguments.USE_CHAT_FAST_BYPASS.value() && user != null) {
|
||||
user.sendMessage(message);
|
||||
} else {
|
||||
audiences.player(bukkitPlayer).sendMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -98,7 +98,7 @@ dependencies {
|
||||
}
|
||||
|
||||
compileOnly(libs.floodgate.api)
|
||||
compileOnly(libs.via.version.api)
|
||||
compileOnly(libs.viaversion)
|
||||
compileOnly(libs.netty)
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import ac.grim.grimac.platform.api.scheduler.PlatformScheduler;
|
||||
import ac.grim.grimac.platform.api.sender.Sender;
|
||||
import ac.grim.grimac.platform.api.sender.SenderFactory;
|
||||
import ac.grim.grimac.utils.anticheat.PlayerDataManager;
|
||||
import ac.grim.grimac.utils.common.GrimArguments;
|
||||
import ac.grim.grimac.utils.common.arguments.CommonGrimArguments;
|
||||
import ac.grim.grimac.utils.reflection.ReflectionUtils;
|
||||
import lombok.Getter;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
@@ -67,7 +67,7 @@ public final class GrimAPI {
|
||||
|
||||
// the order matters
|
||||
private static Platform detectPlatform() {
|
||||
Platform override = Platform.getByName(GrimArguments.PLATFORM_OVERRIDE);
|
||||
Platform override = CommonGrimArguments.PLATFORM_OVERRIDE.value();
|
||||
if (override != null) return override;
|
||||
if (ReflectionUtils.hasClass("io.papermc.paper.threadedregions.RegionizedServer")) return Platform.FOLIA;
|
||||
if (ReflectionUtils.hasClass("org.bukkit.Bukkit")) return Platform.BUKKIT;
|
||||
|
||||
@@ -169,6 +169,10 @@ public class Check extends GrimProcessor implements AbstractCheck {
|
||||
packetType == PacketType.Play.Client.WINDOW_CONFIRMATION;
|
||||
}
|
||||
|
||||
public static boolean isAsync(PacketTypeCommon packetType) {
|
||||
return packetType == PacketType.Play.Client.KEEP_ALIVE;
|
||||
}
|
||||
|
||||
public boolean isUpdate(PacketTypeCommon packetType) {
|
||||
return isFlying(packetType)
|
||||
|| packetType == PacketType.Play.Client.CLIENT_TICK_END
|
||||
|
||||
@@ -4,6 +4,8 @@ import ac.grim.grimac.checks.Check;
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.PacketCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.reflection.ViaVersionUtil;
|
||||
import ac.grim.grimac.utils.via.ViaHooks;
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.manager.server.ServerVersion;
|
||||
@@ -12,6 +14,7 @@ import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientCh
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientChatCommandUnsigned;
|
||||
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientChatMessage;
|
||||
|
||||
|
||||
// this can false from click events, but I doubt this would actually
|
||||
// happen unless they're trying to flag, or if the server is set up badly
|
||||
@CheckData(name = "ChatB", description = "Invalid chat message")
|
||||
@@ -24,11 +27,8 @@ public class ChatB extends Check implements PacketCheck {
|
||||
public void onPacketReceive(PacketReceiveEvent event) {
|
||||
if (event.getPacketType() == PacketType.Play.Client.CHAT_MESSAGE) {
|
||||
String message = new WrapperPlayClientChatMessage(event).getMessage();
|
||||
if (message.isEmpty() || !message.trim().equals(message) || message.startsWith("/") && PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_19)) {
|
||||
if (flagAndAlert("message=" + message)) {
|
||||
event.setCancelled(true);
|
||||
player.onPacketCancel();
|
||||
}
|
||||
if (checkChatMessage(message)) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,4 +53,21 @@ public class ChatB extends Check implements PacketCheck {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// returns whether the packet should be cancelled
|
||||
public boolean checkChatMessage(String message) {
|
||||
if (message.isEmpty() || !message.trim().equals(message) || message.startsWith("/") && PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_19)) {
|
||||
if (flagAndAlert("message=" + message) && shouldModifyPackets()) {
|
||||
player.onPacketCancel();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static {
|
||||
if (ViaVersionUtil.isAvailable && PacketEvents.getAPI().getServerManager().getVersion().isOlderThan(ServerVersion.V_1_11)) {
|
||||
ViaHooks.injectVia();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ public class MultiActionsE extends Check implements PacketCheck {
|
||||
}
|
||||
}
|
||||
|
||||
if (event.getPacketType() != PacketType.Play.Client.KEEP_ALIVE) {
|
||||
if (!isAsync(event.getPacketType())) {
|
||||
dropping = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ public class PacketOrderB extends Check implements PacketCheck {
|
||||
return; // do not set sentAnimation to false
|
||||
}
|
||||
|
||||
if (event.getPacketType() != PacketType.Play.Client.KEEP_ALIVE) {
|
||||
if (!isAsync(event.getPacketType())) {
|
||||
if (sentAttack && is1_9) {
|
||||
flagAndAlert("post-attack");
|
||||
}
|
||||
|
||||
@@ -29,9 +29,7 @@ public class PacketOrderO extends Check implements PacketCheck {
|
||||
return;
|
||||
}
|
||||
|
||||
if (flying && event.getPacketType() != PacketType.Play.Client.KEEP_ALIVE
|
||||
&& event.getPacketType() != PacketType.Play.Client.VEHICLE_MOVE
|
||||
) {
|
||||
if (flying && !isAsync(event.getPacketType()) && event.getPacketType() != PacketType.Play.Client.VEHICLE_MOVE) {
|
||||
if (player.inVehicle() && event.getPacketType() == PacketType.Play.Client.ENTITY_ACTION) {
|
||||
WrapperPlayClientEntityAction.Action action = new WrapperPlayClientEntityAction(event).getAction();
|
||||
if (action == WrapperPlayClientEntityAction.Action.START_SPRINTING || action == WrapperPlayClientEntityAction.Action.STOP_SPRINTING) {
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package ac.grim.grimac.checks.impl.packetorder;
|
||||
|
||||
import ac.grim.grimac.checks.Check;
|
||||
import ac.grim.grimac.checks.CheckData;
|
||||
import ac.grim.grimac.checks.type.PacketCheck;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||
import com.github.retrooper.packetevents.event.PacketSendEvent;
|
||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerBundle;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
|
||||
@CheckData(name = "PacketOrderP", experimental = true)
|
||||
public class PacketOrderP extends Check implements PacketCheck {
|
||||
public PacketOrderP(final GrimPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
private byte trimTimer; // let the list shrink eventually
|
||||
private final IntArrayList transactions = new IntArrayList(0);
|
||||
|
||||
@Override
|
||||
public void onPacketReceive(PacketReceiveEvent event) {
|
||||
if (event.getPacketType() == PacketType.Play.Client.CHUNK_BATCH_ACK) {
|
||||
if (!transactions.rem(player.getLastTransactionReceived())) {
|
||||
flagAndAlert("invalid response");
|
||||
}
|
||||
} else if (!isAsync(event.getPacketType()) && !isTransaction(event.getPacketType())) {
|
||||
if (transactions.rem(player.getLastTransactionReceived())) {
|
||||
flagAndAlert("skipped response, type=" + event.getPacketType());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onPacketSend(PacketSendEvent event) {
|
||||
if (event.getPacketType() == PacketType.Play.Server.CHUNK_BATCH_END) {
|
||||
boolean sendingBundlePacket = player.packetStateData.sendingBundlePacket;
|
||||
if (!sendingBundlePacket) player.user.sendPacket(new WrapperPlayServerBundle());
|
||||
|
||||
player.sendTransaction();
|
||||
int transaction = player.getLastTransactionSent();
|
||||
transactions.add(transaction);
|
||||
if (++trimTimer == 0) transactions.trim();
|
||||
player.addRealTimeTaskNext(() -> {
|
||||
if (transactions.rem(transaction))
|
||||
flagAndAlert("skipped response, type=TRANSACTION");
|
||||
});
|
||||
|
||||
if (!sendingBundlePacket) {
|
||||
event.getTasksAfterSend().add(() -> player.user.sendPacket(new WrapperPlayServerBundle()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,8 +91,7 @@ public class ExplosionHandler extends Check implements PostPredictionCheck {
|
||||
}
|
||||
|
||||
// Otherwise try and flip/open it.
|
||||
final Object poweredValue = state.getInternalData().get(StateValue.POWERED);
|
||||
final boolean canFlip = (poweredValue != null && !(Boolean) poweredValue) || type == StateTypes.LEVER;
|
||||
final boolean canFlip = state.hasProperty(StateValue.POWERED) && !state.isPowered() || type == StateTypes.LEVER;
|
||||
if (canFlip) {
|
||||
player.compensatedWorld.tickOpenable(record.x, record.y, record.z);
|
||||
}
|
||||
|
||||
@@ -69,6 +69,8 @@ public class KnockbackHandler extends Check implements PostPredictionCheck {
|
||||
event.markForReEncode(true);
|
||||
}
|
||||
|
||||
playerVelocity = VectorPrecisionConverter.convert(player.getClientVersion(), playerVelocity);
|
||||
|
||||
// Wrap velocity between two transactions
|
||||
player.sendTransaction();
|
||||
addPlayerKnockback(entityId, player.lastTransactionSent.get(), new Vector3dm(playerVelocity.getX(), playerVelocity.getY(), playerVelocity.getZ()));
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package ac.grim.grimac.checks.impl.velocity;
|
||||
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.github.retrooper.packetevents.manager.server.ServerVersion;
|
||||
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
|
||||
import com.github.retrooper.packetevents.protocol.util.LpVector3d;
|
||||
import com.github.retrooper.packetevents.util.Vector3d;
|
||||
import com.github.retrooper.packetevents.wrapper.PacketWrapper;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
@UtilityClass
|
||||
public class VectorPrecisionConverter {
|
||||
|
||||
private static final ServerVersion SERVER_VERSION = PacketEvents.getAPI().getServerManager().getVersion();
|
||||
|
||||
public static Vector3d convert(ClientVersion version, Vector3d vector) {
|
||||
if (version.isNewerThanOrEquals(ClientVersion.V_1_21_9) && SERVER_VERSION.isOlderThanOrEquals(ServerVersion.V_1_21_8)) {
|
||||
return VectorPrecisionConverter.legacyToLp(vector);
|
||||
} else if (version.isOlderThanOrEquals(ClientVersion.V_1_21_7) && SERVER_VERSION.isNewerThanOrEquals(ServerVersion.V_1_21_9)) {
|
||||
return VectorPrecisionConverter.lpToLegacy(vector);
|
||||
}
|
||||
|
||||
return vector;
|
||||
}
|
||||
|
||||
public static Vector3d legacyToLp(Vector3d legacy) {
|
||||
PacketWrapper<?> wrapper = PacketWrapper.createUniversalPacketWrapper(Unpooled.buffer());
|
||||
LpVector3d.write(wrapper, legacy);
|
||||
return LpVector3d.read(wrapper);
|
||||
}
|
||||
|
||||
private static final double PRECISION_LOSS_FIX = 1e-11d;
|
||||
|
||||
public static Vector3d lpToLegacy(Vector3d lp) {
|
||||
int xi = (int) (lp.x * 8000d + Math.copySign(PRECISION_LOSS_FIX, lp.x));
|
||||
int yi = (int) (lp.y * 8000d + Math.copySign(PRECISION_LOSS_FIX, lp.y));
|
||||
int zi = (int) (lp.z * 8000d + Math.copySign(PRECISION_LOSS_FIX, lp.z));
|
||||
|
||||
short x = (short) xi;
|
||||
short y = (short) yi;
|
||||
short z = (short) zi;
|
||||
|
||||
return new Vector3d(
|
||||
x / 8000d,
|
||||
y / 8000d,
|
||||
z / 8000d
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -46,12 +46,19 @@ public class GrimDebug implements BuildableCommand {
|
||||
PlayerSelector playerSelector = context.getOrDefault("target", null);
|
||||
|
||||
GrimPlayer targetGrimPlayer = parseTarget(sender, playerSelector == null ? sender : playerSelector.getSinglePlayer());
|
||||
if (targetGrimPlayer == null) return;
|
||||
if (targetGrimPlayer == null) {
|
||||
sender.sendMessage(MessageUtil.getParsedComponent(sender, "player-not-found", "%prefix% &cPlayer is exempt or offline!"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (sender.isConsole()) {
|
||||
targetGrimPlayer.checkManager.getDebugHandler().toggleConsoleOutput();
|
||||
} else if (sender.isPlayer()) {
|
||||
GrimPlayer senderGrimPlayer = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(sender.getUniqueId());
|
||||
if (senderGrimPlayer == null) {
|
||||
sender.sendMessage(MessageUtil.getParsedComponent(sender, "sender-not-found", "%prefix% &cYou cannot be exempt to use this command!"));
|
||||
return;
|
||||
}
|
||||
targetGrimPlayer.checkManager.getDebugHandler().toggleListener(senderGrimPlayer);
|
||||
} else {
|
||||
sender.sendMessage(MessageUtil.getParsedComponent(sender,
|
||||
|
||||
@@ -6,7 +6,7 @@ import ac.grim.grimac.manager.init.start.SuperDebug;
|
||||
import ac.grim.grimac.platform.api.sender.Sender;
|
||||
import ac.grim.grimac.utils.anticheat.LogUtil;
|
||||
import ac.grim.grimac.utils.anticheat.MessageUtil;
|
||||
import ac.grim.grimac.utils.common.GrimArguments;
|
||||
import ac.grim.grimac.utils.common.arguments.CommonGrimArguments;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
import org.incendo.cloud.context.CommandContext;
|
||||
@@ -39,7 +39,7 @@ public class GrimLog implements BuildableCommand {
|
||||
}
|
||||
|
||||
private static void sendLog(Sender sender, String log, String success, String failure, Consumer<String> consumer, String type) throws IOException {
|
||||
URL mUrl = new URL(GrimArguments.PASTE_URL + "data/post");
|
||||
URL mUrl = new URL(CommonGrimArguments.PASTE_URL.value() + "data/post");
|
||||
HttpURLConnection urlConn = (HttpURLConnection) mUrl.openConnection();
|
||||
try {
|
||||
urlConn.setDoOutput(true);
|
||||
@@ -53,7 +53,7 @@ public class GrimLog implements BuildableCommand {
|
||||
final int response = urlConn.getResponseCode();
|
||||
if (response == HttpURLConnection.HTTP_CREATED) {
|
||||
String responseURL = urlConn.getHeaderField("Location");
|
||||
String message = success.replace("%url%", GrimArguments.PASTE_URL + responseURL);
|
||||
String message = success.replace("%url%", CommonGrimArguments.PASTE_URL.value() + responseURL);
|
||||
consumer.accept(message);
|
||||
message = MessageUtil.replacePlaceholders(sender, message);
|
||||
sender.sendMessage(MessageUtil.miniMessage(message));
|
||||
|
||||
@@ -5,7 +5,7 @@ import ac.grim.grimac.command.BuildableCommand;
|
||||
import ac.grim.grimac.platform.api.sender.Sender;
|
||||
import ac.grim.grimac.utils.anticheat.LogUtil;
|
||||
import ac.grim.grimac.utils.anticheat.MessageUtil;
|
||||
import ac.grim.grimac.utils.common.GrimArguments;
|
||||
import ac.grim.grimac.utils.common.arguments.CommonGrimArguments;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import lombok.AllArgsConstructor;
|
||||
@@ -56,7 +56,7 @@ public class GrimVersion implements BuildableCommand {
|
||||
try {
|
||||
//
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(GrimArguments.API_URL + "updates"))
|
||||
.uri(URI.create(CommonGrimArguments.API_URL.value() + "updates"))
|
||||
.GET()
|
||||
.header("User-Agent", "GrimAC/" + GrimAPI.INSTANCE.getExternalAPI().getGrimVersion())
|
||||
.header("Content-Type", "application/json")
|
||||
@@ -65,11 +65,11 @@ public class GrimVersion implements BuildableCommand {
|
||||
|
||||
HttpResponse<String> response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
final int statusCode = response.statusCode();
|
||||
if (statusCode != 200) {
|
||||
if (statusCode < 200 || statusCode >= 300) {
|
||||
Component msg = updateMessage.get();
|
||||
sender.sendMessage(Objects.requireNonNullElseGet(msg, () -> Component.text()
|
||||
.append(MessageUtil.miniMessage("%prefix%"))
|
||||
.append(Component.text(" Failed to check latest GrimAC version. Update server responded with code: ")
|
||||
.append(Component.text(" Failed to check latest GrimAC version. Update server responded with status code: ")
|
||||
.color(NamedTextColor.YELLOW))
|
||||
.append(Component.text(statusCode)
|
||||
.color(getColorForStatusCode(statusCode))
|
||||
|
||||
@@ -68,7 +68,7 @@ public class CheckManagerListener extends PacketListenerAbstract {
|
||||
if (Materials.isPlaceableWaterBucket(blockPlace.itemStack.getType()) && PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_13)) {
|
||||
blockPlace.replaceClicked = true; // See what's in the existing place
|
||||
WrappedBlockState existing = blockPlace.getExistingBlockData();
|
||||
if (!(boolean) existing.getInternalData().getOrDefault(StateValue.WATERLOGGED, true)) {
|
||||
if (existing.hasProperty(StateValue.WATERLOGGED) && !existing.isWaterlogged()) {
|
||||
// Strangely, the client does not predict waterlogged placements
|
||||
didPlace = true;
|
||||
}
|
||||
@@ -308,7 +308,7 @@ public class CheckManagerListener extends PacketListenerAbstract {
|
||||
|
||||
if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_13)) {
|
||||
WrappedBlockState existing = blockPlace.getExistingBlockData();
|
||||
if (existing.getInternalData().containsKey(StateValue.WATERLOGGED)) { // waterloggable
|
||||
if (existing.hasProperty(StateValue.WATERLOGGED)) { // waterloggable
|
||||
existing.setWaterlogged(false);
|
||||
blockPlace.set(existing);
|
||||
placed = true;
|
||||
@@ -599,6 +599,10 @@ public class CheckManagerListener extends PacketListenerAbstract {
|
||||
player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), () -> player.serverOpenedInventoryThisTick = true);
|
||||
}
|
||||
|
||||
if (event.getPacketType() == PacketType.Play.Server.BUNDLE) {
|
||||
player.packetStateData.sendingBundlePacket = !player.packetStateData.sendingBundlePacket;
|
||||
}
|
||||
|
||||
player.checkManager.onPacketSend(event);
|
||||
}
|
||||
|
||||
|
||||
@@ -124,6 +124,7 @@ public class CheckManager {
|
||||
.put(PacketOrderC.class, new PacketOrderC(player))
|
||||
.put(PacketOrderD.class, new PacketOrderD(player))
|
||||
.put(PacketOrderO.class, new PacketOrderO(player))
|
||||
.put(PacketOrderP.class, new PacketOrderP(player))
|
||||
.put(SprintA.class, new SprintA(player))
|
||||
.put(VehicleA.class, new VehicleA(player))
|
||||
.put(VehicleB.class, new VehicleB(player))
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
package ac.grim.grimac.manager;
|
||||
|
||||
import ac.grim.grimac.GrimAPI;
|
||||
import ac.grim.grimac.api.config.ConfigManager;
|
||||
import ac.grim.grimac.manager.init.ReloadableInitable;
|
||||
import ac.grim.grimac.manager.init.start.StartableInitable;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.anticheat.LogUtil;
|
||||
import ac.grim.grimac.utils.anticheat.MessageUtil;
|
||||
import ac.grim.grimac.utils.webhook.Embed;
|
||||
import ac.grim.grimac.utils.webhook.EmbedField;
|
||||
import ac.grim.grimac.utils.webhook.EmbedFooter;
|
||||
import ac.grim.grimac.utils.webhook.WebhookMessage;
|
||||
import ac.grim.grimac.utils.webhook.*;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
import java.awt.*;
|
||||
import java.net.URI;
|
||||
@@ -18,7 +20,6 @@ import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@@ -26,8 +27,8 @@ import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class DiscordManager implements StartableInitable, ReloadableInitable {
|
||||
public static final Predicate<String> WEBHOOK_REGEX = Pattern.compile("https://discord.com/api/webhooks/\\d+/[\\w-]+").asMatchPredicate();
|
||||
public static final Duration timeout = Duration.ofSeconds(15);
|
||||
private static final Predicate<String> WEBHOOK_REGEX = Pattern.compile("https://discord.com/api/webhooks/\\d+/[\\w-]+").asMatchPredicate();
|
||||
private static final Duration timeout = Duration.ofSeconds(15);
|
||||
private static final HttpClient client = HttpClient.newBuilder().connectTimeout(timeout).build();
|
||||
private static final ConcurrentLinkedDeque<HttpRequest> requests = new ConcurrentLinkedDeque<>();
|
||||
private static final AtomicBoolean taskStarted = new AtomicBoolean();
|
||||
@@ -38,6 +39,24 @@ public class DiscordManager implements StartableInitable, ReloadableInitable {
|
||||
private String staticContent = "";
|
||||
private String embedTitle = "";
|
||||
private boolean includeTimestamp;
|
||||
private boolean includeVerbose;
|
||||
private @Nullable String embedImageUrl;
|
||||
private @Nullable String embedThumbnailUrl;
|
||||
private @Nullable String embedFooterUrl;
|
||||
private String embedFooterText = "";
|
||||
|
||||
private static final Pattern URL_PATTERN = Pattern.compile("^https?://(?:www\\.)?[-a-z0-9@:%._+~#=]{1,256}\\.[a-z0-9()]{1,6}\\b[-a-z0-9()@:%_+.~#?&/=]*$", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
private static String validatedConfigURL(String configPath, String defaultURL) {
|
||||
String url = GrimAPI.INSTANCE.getConfigManager().getConfig().getStringElse("embed-image-url", defaultURL);
|
||||
if (url == null || url.isBlank()) return null;
|
||||
if (URL_PATTERN.matcher(url).matches()) {
|
||||
return url;
|
||||
} else {
|
||||
LogUtil.warn("Invalid embed url for config path " + configPath + ": " + configPath);
|
||||
return defaultURL;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
@@ -47,12 +66,13 @@ public class DiscordManager implements StartableInitable, ReloadableInitable {
|
||||
@Override
|
||||
public void reload() {
|
||||
try {
|
||||
if (!GrimAPI.INSTANCE.getConfigManager().getConfig().getBooleanElse("enabled", false)) {
|
||||
ConfigManager config = GrimAPI.INSTANCE.getConfigManager().getConfig();
|
||||
if (!config.getBooleanElse("enabled", false)) {
|
||||
url = null;
|
||||
return;
|
||||
}
|
||||
|
||||
String webhook = GrimAPI.INSTANCE.getConfigManager().getConfig().getStringElse("webhook", "");
|
||||
String webhook = config.getStringElse("webhook", "");
|
||||
|
||||
if (!WEBHOOK_REGEX.test(webhook)) {
|
||||
LogUtil.error("Discord webhook url does not follow expected format (https://discord.com/api/webhooks/<id>/<token>): " + webhook);
|
||||
@@ -60,36 +80,43 @@ public class DiscordManager implements StartableInitable, ReloadableInitable {
|
||||
} else {
|
||||
url = new URI(webhook);
|
||||
}
|
||||
|
||||
embedTitle = GrimAPI.INSTANCE.getConfigManager().getConfig().getStringElse("embed-title", "**Grim Alert**");
|
||||
// not adding these to the config since they may change in the future
|
||||
// mainly for just for allowing more customization
|
||||
embedImageUrl = validatedConfigURL("embed-image-url", null);
|
||||
embedThumbnailUrl = validatedConfigURL("embed-thumbnail-url", "https://crafthead.net/helm/%uuid%");
|
||||
embedFooterUrl = validatedConfigURL("embed-footer-url", "https://grim.ac/images/grim.png");
|
||||
embedFooterText = config.getStringElse("embed-footer-text", "v%grim_version%");
|
||||
embedTitle = config.getStringElse("embed-title", "**Grim Alert**");
|
||||
|
||||
try {
|
||||
embedColor = Color.decode(GrimAPI.INSTANCE.getConfigManager().getConfig().getStringElse("embed-color", "#00FFFF")).getRGB();
|
||||
embedColor = Color.decode(config.getStringElse("embed-color", "#00FFFF")).getRGB();
|
||||
} catch (NumberFormatException e) {
|
||||
LogUtil.warn("Discord embed color is invalid");
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String string : GrimAPI.INSTANCE.getConfigManager().getConfig().getStringListElse("violation-content", getDefaultContents())) {
|
||||
for (String string : config.getStringListElse("violation-content", getDefaultContents())) {
|
||||
sb.append(string).append("\n");
|
||||
}
|
||||
staticContent = sb.toString();
|
||||
includeTimestamp = GrimAPI.INSTANCE.getConfigManager().getConfig().getBooleanElse("include-timestamp", true);
|
||||
includeTimestamp = config.getBooleanElse("include-timestamp", true);
|
||||
includeVerbose = config.getBooleanElse("include-verbose", true);
|
||||
} catch (Exception e) {
|
||||
LogUtil.error("Failed to load Discord webhook configuration", e);
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> getDefaultContents() {
|
||||
List<String> list = new ArrayList<>();
|
||||
list.add("**Player**: %player%");
|
||||
list.add("**Check**: %check%");
|
||||
list.add("**Violations**: %violations%");
|
||||
list.add("**Client Version**: %version%");
|
||||
list.add("**Brand**: %brand%");
|
||||
list.add("**Ping**: %ping%");
|
||||
list.add("**TPS**: %tps%");
|
||||
return list;
|
||||
@Contract(value = " -> new", pure = true)
|
||||
private @NotNull @Unmodifiable List<@NotNull String> getDefaultContents() {
|
||||
return List.of(
|
||||
"**Player**: %player%",
|
||||
"**Check**: %check%",
|
||||
"**Violations**: %violations%",
|
||||
"**Client Version**: %version%",
|
||||
"**Brand**: %brand%",
|
||||
"**Ping**: %ping%",
|
||||
"**TPS**: %tps%"
|
||||
);
|
||||
}
|
||||
|
||||
public void sendAlert(GrimPlayer player, String verbose, String checkName, int violations) {
|
||||
@@ -103,17 +130,18 @@ public class DiscordManager implements StartableInitable, ReloadableInitable {
|
||||
content = MessageUtil.replacePlaceholders(player, content, true);
|
||||
|
||||
Embed embed = new Embed(content)
|
||||
.imageURL("https://i.stack.imgur.com/Fzh0w.png")
|
||||
.thumbnailURL("https://crafthead.net/helm/" + player.user.getProfile().getUUID())
|
||||
.color(embedColor)
|
||||
.title(embedTitle)
|
||||
.footer(new EmbedFooter("", "https://grim.ac/images/grim.png"));
|
||||
.imageURL(MessageUtil.replacePlaceholders(player, embedImageUrl, false))
|
||||
.thumbnailURL(MessageUtil.replacePlaceholders(player, embedThumbnailUrl, false))
|
||||
.footer(new EmbedFooter(
|
||||
MessageUtil.replacePlaceholders(player, embedFooterText, true),
|
||||
MessageUtil.replacePlaceholders(player, embedFooterUrl, false)
|
||||
));
|
||||
|
||||
if (includeTimestamp) {
|
||||
embed.timestamp(Instant.now());
|
||||
}
|
||||
if (includeTimestamp) embed.timestamp(Instant.now());
|
||||
|
||||
if (!verbose.isEmpty()) {
|
||||
if (!verbose.isEmpty() && includeVerbose) {
|
||||
embed.addFields(new EmbedField("Verbose", MessageUtil.filterDiscordText(verbose), true));
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ import ac.grim.grimac.platform.api.player.PlatformPlayer;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.anticheat.LogUtil;
|
||||
import ac.grim.grimac.utils.anticheat.MessageUtil;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
@@ -179,7 +181,7 @@ public class PunishmentManager implements ConfigReloadable {
|
||||
|
||||
group.violations.put(currentTime, check);
|
||||
// Remove violations older than the defined time in the config
|
||||
group.violations.entrySet().removeIf(time -> currentTime - time.getKey() > group.removeViolationsAfter);
|
||||
group.violations.long2ObjectEntrySet().removeIf(time -> currentTime - time.getLongKey() > group.removeViolationsAfter);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,7 +199,7 @@ public class PunishmentManager implements ConfigReloadable {
|
||||
class PunishGroup {
|
||||
public final List<AbstractCheck> checks;
|
||||
public final List<ParsedCommand> commands;
|
||||
public final Map<Long, Check> violations = new HashMap<>();
|
||||
public final Long2ObjectMap<Check> violations = new Long2ObjectOpenHashMap<>();
|
||||
public final int removeViolationsAfter; // time to remove violations after in milliseconds
|
||||
}
|
||||
|
||||
|
||||
@@ -942,14 +942,24 @@ public class GrimPlayer implements GrimUser {
|
||||
if (this.movementThisTick.size() >= 100) {
|
||||
GrimPlayer.Movement movement1 = this.movementThisTick.removeFirst();
|
||||
GrimPlayer.Movement movement2 = this.movementThisTick.removeFirst();
|
||||
GrimPlayer.Movement movement3 = new GrimPlayer.Movement(movement1.from(), movement2.to(), false);
|
||||
GrimPlayer.Movement movement3 = new GrimPlayer.Movement(movement1.from(), movement2.to());
|
||||
this.movementThisTick.addFirst(movement3);
|
||||
}
|
||||
|
||||
this.movementThisTick.add(movement);
|
||||
}
|
||||
|
||||
public record Movement(Vector3d from, Vector3d to, boolean axisIndependant) {}
|
||||
public record Movement(Vector3d from, Vector3d to, Vector3d axisDependentOriginalMovement) {
|
||||
|
||||
public Movement(Vector3d from, Vector3d to) {
|
||||
this(from, to, null);
|
||||
}
|
||||
|
||||
public boolean axisIndependant() {
|
||||
return axisDependentOriginalMovement != null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TODO (Cross-platform) keep track of world at packet level; do not rely on potentially non-lag-compensated platformPlayer.getWorld()
|
||||
public Location getLocation() {
|
||||
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
package ac.grim.grimac.predictionengine.blockeffects;
|
||||
|
||||
import ac.grim.grimac.utils.nmsutil.Collisions;
|
||||
import com.github.retrooper.packetevents.protocol.world.Direction;
|
||||
import com.github.retrooper.packetevents.util.Vector3d;
|
||||
import com.github.retrooper.packetevents.util.Vector3i;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@UtilityClass
|
||||
public class BlockCollisions {
|
||||
|
||||
public static final Vector3d X_AXIS = new Vector3d(1.0, 0.0, 0.0);
|
||||
public static final Vector3d Y_AXIS = new Vector3d(0.0, 1.0, 0.0);
|
||||
public static final Vector3d Z_AXIS = new Vector3d(0.0, 0.0, 1.0);
|
||||
|
||||
public static Vector3i getFurthestCorner(Vector3d vector) {
|
||||
double xDot = Math.abs(X_AXIS.dot(vector));
|
||||
double yDot = Math.abs(Y_AXIS.dot(vector));
|
||||
double zDot = Math.abs(Z_AXIS.dot(vector));
|
||||
int xSign = vector.x >= 0.0 ? 1 : -1;
|
||||
int ySign = vector.y >= 0.0 ? 1 : -1;
|
||||
int zSign = vector.z >= 0.0 ? 1 : -1;
|
||||
if (xDot <= yDot && xDot <= zDot) {
|
||||
return new Vector3i(-xSign, -zSign, ySign);
|
||||
} else {
|
||||
return yDot <= zDot ? new Vector3i(zSign, -ySign, -xSign) : new Vector3i(-ySign, xSign, -zSign);
|
||||
}
|
||||
}
|
||||
|
||||
public static Optional<Vector3d> clip(double minX, double minY, double minZ, double maxX, double maxY, double maxZ, Vector3d start, Vector3d end) {
|
||||
double[] minDistance = new double[]{1.0};
|
||||
double deltaX = end.x - start.x;
|
||||
double deltaY = end.y - start.y;
|
||||
double deltaZ = end.z - start.z;
|
||||
Direction direction = getDirection(minX, minY, minZ, maxX, maxY, maxZ, start, minDistance, null, deltaX, deltaY, deltaZ);
|
||||
if (direction == null) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
double distance = minDistance[0];
|
||||
return Optional.of(start.add(distance * deltaX, distance * deltaY, distance * deltaZ));
|
||||
}
|
||||
}
|
||||
|
||||
private static Direction getDirection(
|
||||
double minX,
|
||||
double minY,
|
||||
double minZ,
|
||||
double maxX,
|
||||
double maxY,
|
||||
double maxZ,
|
||||
Vector3d start,
|
||||
double[] minDistance,
|
||||
Direction facing,
|
||||
double deltaX,
|
||||
double deltaY,
|
||||
double deltaZ
|
||||
) {
|
||||
if (deltaX > Collisions.COLLISION_EPSILON) {
|
||||
facing = clipPoint(minDistance, facing, deltaX, deltaY, deltaZ, minX, minY, maxY, minZ, maxZ, Direction.WEST, start.x, start.y, start.z);
|
||||
} else if (deltaX < -Collisions.COLLISION_EPSILON) {
|
||||
facing = clipPoint(minDistance, facing, deltaX, deltaY, deltaZ, maxX, minY, maxY, minZ, maxZ, Direction.EAST, start.x, start.y, start.z);
|
||||
}
|
||||
|
||||
if (deltaY > Collisions.COLLISION_EPSILON) {
|
||||
facing = clipPoint(minDistance, facing, deltaY, deltaZ, deltaX, minY, minZ, maxZ, minX, maxX, Direction.DOWN, start.y, start.z, start.x);
|
||||
} else if (deltaY < -Collisions.COLLISION_EPSILON) {
|
||||
facing = clipPoint(minDistance, facing, deltaY, deltaZ, deltaX, maxY, minZ, maxZ, minX, maxX, Direction.UP, start.y, start.z, start.x);
|
||||
}
|
||||
|
||||
if (deltaZ > Collisions.COLLISION_EPSILON) {
|
||||
facing = clipPoint(minDistance, facing, deltaZ, deltaX, deltaY, minZ, minX, maxX, minY, maxY, Direction.NORTH, start.z, start.x, start.y);
|
||||
} else if (deltaZ < -Collisions.COLLISION_EPSILON) {
|
||||
facing = clipPoint(minDistance, facing, deltaZ, deltaX, deltaY, maxZ, minX, maxX, minY, maxY, Direction.SOUTH, start.z, start.x, start.y);
|
||||
}
|
||||
|
||||
return facing;
|
||||
}
|
||||
|
||||
public static Direction clipPoint(
|
||||
double[] minDistance,
|
||||
Direction prevDirection,
|
||||
double distanceSide,
|
||||
double distanceOtherA,
|
||||
double distanceOtherB,
|
||||
double minSide,
|
||||
double minOtherA,
|
||||
double maxOtherA,
|
||||
double minOtherB,
|
||||
double maxOtherB,
|
||||
Direction hitSide,
|
||||
double startSide,
|
||||
double startOtherA,
|
||||
double startOtherB
|
||||
) {
|
||||
double sideDistance = (minSide - startSide) / distanceSide;
|
||||
double otherDistanceA = startOtherA + sideDistance * distanceOtherA;
|
||||
double otherDistanceB = startOtherB + sideDistance * distanceOtherB;
|
||||
if (sideDistance > 0.0 && sideDistance < minDistance[0]
|
||||
&& minOtherA - Collisions.COLLISION_EPSILON < otherDistanceA
|
||||
&& otherDistanceA < maxOtherA + Collisions.COLLISION_EPSILON
|
||||
&& minOtherB - Collisions.COLLISION_EPSILON < otherDistanceB
|
||||
&& otherDistanceB < maxOtherB + Collisions.COLLISION_EPSILON) {
|
||||
minDistance[0] = sideDistance;
|
||||
return hitSide;
|
||||
} else {
|
||||
return prevDirection;
|
||||
}
|
||||
}
|
||||
|
||||
public static final ImmutableList<Collisions.Axis> YXZ_AXIS_ORDER = ImmutableList.of(Collisions.Axis.Y, Collisions.Axis.X, Collisions.Axis.Z);
|
||||
public static final ImmutableList<Collisions.Axis> YZX_AXIS_ORDER = ImmutableList.of(Collisions.Axis.Y, Collisions.Axis.Z, Collisions.Axis.X);
|
||||
|
||||
public static ImmutableList<Collisions.Axis> axisStepOrder(Vector3d vector) {
|
||||
return Math.abs(vector.getX()) < Math.abs(vector.getZ()) ? YZX_AXIS_ORDER : YXZ_AXIS_ORDER;
|
||||
}
|
||||
|
||||
public static Vector3d relative(Vector3d curr, Direction direction, double value) {
|
||||
Vector3i vec = direction.getVector();
|
||||
return new Vector3d(
|
||||
curr.x + value * vec.getX(), curr.y + value * vec.getY(), curr.z + value * vec.getZ()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package ac.grim.grimac.predictionengine.blockeffects;
|
||||
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
|
||||
public interface BlockEffectsResolver {
|
||||
|
||||
void applyEffectsFromBlocks(GrimPlayer player);
|
||||
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package ac.grim.grimac.predictionengine.blockeffects;
|
||||
|
||||
import com.github.retrooper.packetevents.util.Vector3i;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface BlockStepVisitor {
|
||||
|
||||
boolean visit(Vector3i blockPos, int index);
|
||||
|
||||
}
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
package ac.grim.grimac.predictionengine.blockeffects.impl;
|
||||
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.predictionengine.blockeffects.BlockCollisions;
|
||||
import ac.grim.grimac.predictionengine.blockeffects.BlockEffectsResolver;
|
||||
import ac.grim.grimac.predictionengine.blockeffects.BlockStepVisitor;
|
||||
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
|
||||
import ac.grim.grimac.utils.math.GrimMath;
|
||||
import ac.grim.grimac.utils.nmsutil.Collisions;
|
||||
import ac.grim.grimac.utils.nmsutil.GetBoundingBox;
|
||||
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
|
||||
import com.github.retrooper.packetevents.protocol.world.states.type.StateType;
|
||||
import com.github.retrooper.packetevents.util.Vector3d;
|
||||
import com.github.retrooper.packetevents.util.Vector3i;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.longs.LongSet;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
// 1.21.10 ONLY - 1.21.9 is impossible to implement due to Mojang awesomeness
|
||||
public class BlockEffectsResolverV1_21_10 implements BlockEffectsResolver {
|
||||
|
||||
public static final BlockEffectsResolver INSTANCE = new BlockEffectsResolverV1_21_10();
|
||||
|
||||
@Override
|
||||
public void applyEffectsFromBlocks(GrimPlayer player) {
|
||||
LongSet visitedBlocks = player.visitedBlocks;
|
||||
|
||||
for (GrimPlayer.Movement movement : player.finalMovementsThisTick) {
|
||||
Vector3d from = movement.from();
|
||||
Vector3d to = movement.to().subtract(movement.from());
|
||||
|
||||
int iterationCount = 16;
|
||||
if (movement.axisIndependant() && to.lengthSquared() > 0.0) {
|
||||
for (Collisions.Axis axis : BlockCollisions.axisStepOrder(movement.axisDependentOriginalMovement())) {
|
||||
double value = axis.get(to);
|
||||
if (value != 0.0) {
|
||||
Vector3d vector = BlockCollisions.relative(from, axis.getPositive(), value);
|
||||
iterationCount -= checkInsideBlocks(player, from, vector, visitedBlocks, iterationCount);
|
||||
from = vector;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
iterationCount -= checkInsideBlocks(player, movement.from(), movement.to(), visitedBlocks, 16);
|
||||
}
|
||||
|
||||
if (iterationCount <= 0) {
|
||||
checkInsideBlocks(player, movement.to(), movement.to(), visitedBlocks, 1);
|
||||
}
|
||||
}
|
||||
|
||||
visitedBlocks.clear();
|
||||
}
|
||||
|
||||
public static int checkInsideBlocks(GrimPlayer player, Vector3d from, Vector3d to, LongSet visitedBlocks, int count) {
|
||||
SimpleCollisionBox boundingBox = GetBoundingBox.getCollisionBoxForPlayer(player, to.x, to.y, to.z).expand(-1.0E-5F);
|
||||
boolean isFarEnough = from.distanceSquared(to) > GrimMath.square(0.9999900000002526);
|
||||
|
||||
AtomicInteger blockCount = new AtomicInteger();
|
||||
forEachBlockIntersectedBetween(from, to, boundingBox, (blockPos, localCount) -> {
|
||||
if (localCount >= count) {
|
||||
return false;
|
||||
}
|
||||
|
||||
blockCount.set(localCount);
|
||||
|
||||
WrappedBlockState blockState = player.compensatedWorld.getBlock(blockPos);
|
||||
StateType blockType = blockState.getType();
|
||||
|
||||
if (blockType.isAir()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (visitedBlocks.add(GrimMath.asLong(blockPos))) {
|
||||
boolean shouldApply = isFarEnough || boundingBox.intersects(blockPos);
|
||||
Collisions.onInsideBlock(player, blockType, blockState, blockPos.x, blockPos.y, blockPos.z, shouldApply);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
return blockCount.get() + 1;
|
||||
}
|
||||
|
||||
public static boolean forEachBlockIntersectedBetween(Vector3d start, Vector3d end, SimpleCollisionBox boundingBox, BlockStepVisitor visitor) {
|
||||
Vector3d direction = end.subtract(start);
|
||||
if (direction.lengthSquared() < (double) GrimMath.square(1.0E-5F)) {
|
||||
for (Vector3i blockPos : SimpleCollisionBox.betweenClosed(boundingBox)) {
|
||||
if (!visitor.visit(blockPos, 0)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
LongSet alreadyVisited = new LongOpenHashSet();
|
||||
|
||||
for (Vector3i blockPos : SimpleCollisionBox.betweenCornersInDirection(boundingBox.move(direction.multiply(-1.0)), direction)) {
|
||||
if (!visitor.visit(blockPos, 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
alreadyVisited.add(GrimMath.asLong(blockPos));
|
||||
}
|
||||
|
||||
int iterationCount = addCollisionsAlongTravel(alreadyVisited, direction, boundingBox, visitor);
|
||||
if (iterationCount < 0) {
|
||||
return false;
|
||||
} else {
|
||||
for (Vector3i blockPos : SimpleCollisionBox.betweenCornersInDirection(boundingBox, direction)) {
|
||||
if (alreadyVisited.add(GrimMath.asLong(blockPos)) && !visitor.visit(blockPos, iterationCount + 1)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int addCollisionsAlongTravel(LongSet alreadyVisited, Vector3d direction, SimpleCollisionBox boundingBox, BlockStepVisitor visitor) {
|
||||
double sizeX = boundingBox.getXSize();
|
||||
double sizeY = boundingBox.getYSize();
|
||||
double sizeZ = boundingBox.getZSize();
|
||||
|
||||
Vector3i furthestCorner = BlockCollisions.getFurthestCorner(direction);
|
||||
|
||||
Vector3d center = boundingBox.getCenter();
|
||||
Vector3d end = new Vector3d(
|
||||
center.x + sizeX * 0.5 * furthestCorner.getX(),
|
||||
center.y + sizeY * 0.5 * furthestCorner.getY(),
|
||||
center.z + sizeZ * 0.5 * furthestCorner.getZ()
|
||||
);
|
||||
Vector3d start = end.subtract(direction);
|
||||
|
||||
int currentX = GrimMath.floor(start.x);
|
||||
int currentY = GrimMath.floor(start.y);
|
||||
int currentZ = GrimMath.floor(start.z);
|
||||
int stepX = GrimMath.sign(direction.x);
|
||||
int stepY = GrimMath.sign(direction.y);
|
||||
int stepZ = GrimMath.sign(direction.z);
|
||||
double tMaxX = stepX == 0 ? Double.MAX_VALUE : stepX / direction.x;
|
||||
double tMaxY = stepY == 0 ? Double.MAX_VALUE : stepY / direction.y;
|
||||
double tMaxZ = stepZ == 0 ? Double.MAX_VALUE : stepZ / direction.z;
|
||||
double tDeltaX = tMaxX * (stepX > 0 ? 1.0 - GrimMath.frac(start.x) : GrimMath.frac(start.x));
|
||||
double tDeltaY = tMaxY * (stepY > 0 ? 1.0 - GrimMath.frac(start.y) : GrimMath.frac(start.y));
|
||||
double tDeltaZ = tMaxZ * (stepZ > 0 ? 1.0 - GrimMath.frac(start.z) : GrimMath.frac(start.z));
|
||||
int iterationCount = 0;
|
||||
|
||||
while (tDeltaX <= 1.0 || tDeltaY <= 1.0 || tDeltaZ <= 1.0) {
|
||||
if (tDeltaX < tDeltaY) {
|
||||
if (tDeltaX < tDeltaZ) {
|
||||
currentX += stepX;
|
||||
tDeltaX += tMaxX;
|
||||
} else {
|
||||
currentZ += stepZ;
|
||||
tDeltaZ += tMaxZ;
|
||||
}
|
||||
} else if (tDeltaY < tDeltaZ) {
|
||||
currentY += stepY;
|
||||
tDeltaY += tMaxY;
|
||||
} else {
|
||||
currentZ += stepZ;
|
||||
tDeltaZ += tMaxZ;
|
||||
}
|
||||
|
||||
Optional<Vector3d> collisionPoint = BlockCollisions.clip(currentX, currentY, currentZ, currentX + 1, currentY + 1, currentZ + 1, start, end);
|
||||
if (!collisionPoint.isEmpty()) {
|
||||
iterationCount++;
|
||||
Vector3d collisionVec = collisionPoint.get();
|
||||
double clampedX = GrimMath.clamp(collisionVec.x, currentX + 1.0E-5F, currentX + 1.0 - 1.0E-5F);
|
||||
double clampedY = GrimMath.clamp(collisionVec.y, currentY + 1.0E-5F, currentY + 1.0 - 1.0E-5F);
|
||||
double clampedZ = GrimMath.clamp(collisionVec.z, currentZ + 1.0E-5F, currentZ + 1.0 - 1.0E-5F);
|
||||
int endX = GrimMath.floor(clampedX - sizeX * furthestCorner.getX());
|
||||
int endY = GrimMath.floor(clampedY - sizeY * furthestCorner.getY());
|
||||
int endZ = GrimMath.floor(clampedZ - sizeZ * furthestCorner.getZ());
|
||||
int copyIterationCount = iterationCount;
|
||||
|
||||
for (Vector3i blockPos : SimpleCollisionBox.betweenCornersInDirection(currentX, currentY, currentZ, endX, endY, endZ, direction)) {
|
||||
if (alreadyVisited.add(GrimMath.asLong(blockPos)) && !visitor.visit(blockPos, copyIterationCount)) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return iterationCount;
|
||||
}
|
||||
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
package ac.grim.grimac.predictionengine.blockeffects.impl;
|
||||
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.predictionengine.blockeffects.BlockCollisions;
|
||||
import ac.grim.grimac.predictionengine.blockeffects.BlockEffectsResolver;
|
||||
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
|
||||
import ac.grim.grimac.utils.math.GrimMath;
|
||||
import ac.grim.grimac.utils.nmsutil.Collisions;
|
||||
import ac.grim.grimac.utils.nmsutil.GetBoundingBox;
|
||||
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
|
||||
import com.github.retrooper.packetevents.protocol.world.states.type.StateType;
|
||||
import com.github.retrooper.packetevents.util.Vector3d;
|
||||
import com.github.retrooper.packetevents.util.Vector3i;
|
||||
import it.unimi.dsi.fastutil.longs.LongSet;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
// 1.21.2-1.21.3
|
||||
public class BlockEffectsResolverV1_21_2 implements BlockEffectsResolver {
|
||||
|
||||
public static BlockEffectsResolver INSTANCE = new BlockEffectsResolverV1_21_2();
|
||||
|
||||
@Override
|
||||
public void applyEffectsFromBlocks(GrimPlayer player) {
|
||||
LongSet visitedBlocks = player.visitedBlocks;
|
||||
SimpleCollisionBox boundingBox = (player.inVehicle()
|
||||
? GetBoundingBox.getCollisionBoxForPlayer(player, player.x, player.y, player.z)
|
||||
: player.boundingBox.copy()).expand(-1.0E-5F);
|
||||
|
||||
for (GrimPlayer.Movement movement : player.finalMovementsThisTick) {
|
||||
Vector3d from = movement.from();
|
||||
Vector3d to = movement.to();
|
||||
|
||||
for (Vector3i blockPos : boxTraverseBlocks(from, to, boundingBox)) {
|
||||
WrappedBlockState blockState = player.compensatedWorld.getBlock(blockPos);
|
||||
StateType blockType = blockState.getType();
|
||||
|
||||
if (blockType.isAir()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (visitedBlocks.add(GrimMath.asLong(blockPos))) {
|
||||
Collisions.onInsideBlock(player, blockType, blockState, blockPos.x, blockPos.y, blockPos.z, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visitedBlocks.clear();
|
||||
}
|
||||
|
||||
private static Iterable<Vector3i> boxTraverseBlocks(Vector3d start, Vector3d end, SimpleCollisionBox boundingBox) {
|
||||
Vector3d direction = end.subtract(start);
|
||||
Iterable<Vector3i> initialBlocks = SimpleCollisionBox.betweenClosed(boundingBox);
|
||||
if (direction.lengthSquared() < GrimMath.square(0.99999F)) {
|
||||
return initialBlocks;
|
||||
} else {
|
||||
Set<Vector3i> traversedBlocks = new ObjectLinkedOpenHashSet<>();
|
||||
Vector3d normalizedDirection = direction.normalize().multiply(Collisions.COLLISION_EPSILON);
|
||||
Vector3d boxMinPosition = boundingBox.min().toVector3d().add(normalizedDirection);
|
||||
Vector3d subtractedMinPosition = boundingBox.min().toVector3d().subtract(direction).subtract(normalizedDirection);
|
||||
addCollisionsAlongTravel(traversedBlocks, subtractedMinPosition, boxMinPosition, boundingBox);
|
||||
|
||||
for (Vector3i blockPos : initialBlocks) {
|
||||
traversedBlocks.add(blockPos);
|
||||
}
|
||||
|
||||
return traversedBlocks;
|
||||
}
|
||||
}
|
||||
|
||||
public static void addCollisionsAlongTravel(Set<Vector3i> output, Vector3d start, Vector3d end, SimpleCollisionBox boundingBox) {
|
||||
Vector3d direction = end.subtract(start);
|
||||
int currentX = GrimMath.floor(start.x);
|
||||
int currentY = GrimMath.floor(start.y);
|
||||
int currentZ = GrimMath.floor(start.z);
|
||||
int stepX = GrimMath.sign(direction.x);
|
||||
int stepY = GrimMath.sign(direction.y);
|
||||
int stepZ = GrimMath.sign(direction.z);
|
||||
double tMaxX = stepX == 0 ? Double.MAX_VALUE : stepX / direction.x;
|
||||
double tMaxY = stepY == 0 ? Double.MAX_VALUE : stepY / direction.y;
|
||||
double tMaxZ = stepZ == 0 ? Double.MAX_VALUE : stepZ / direction.z;
|
||||
double tDeltaX = tMaxX * (stepX > 0 ? 1.0 - GrimMath.frac(start.x) : GrimMath.frac(start.x));
|
||||
double tDeltaY = tMaxY * (stepY > 0 ? 1.0 - GrimMath.frac(start.y) : GrimMath.frac(start.y));
|
||||
double tDeltaZ = tMaxZ * (stepZ > 0 ? 1.0 - GrimMath.frac(start.z) : GrimMath.frac(start.z));
|
||||
int iterationCount = 0;
|
||||
|
||||
while (tDeltaX <= 1.0 || tDeltaY <= 1.0 || tDeltaZ <= 1.0) {
|
||||
if (tDeltaX < tDeltaY) {
|
||||
if (tDeltaX < tDeltaZ) {
|
||||
currentX += stepX;
|
||||
tDeltaX += tMaxX;
|
||||
} else {
|
||||
currentZ += stepZ;
|
||||
tDeltaZ += tMaxZ;
|
||||
}
|
||||
} else if (tDeltaY < tDeltaZ) {
|
||||
currentY += stepY;
|
||||
tDeltaY += tMaxY;
|
||||
} else {
|
||||
currentZ += stepZ;
|
||||
tDeltaZ += tMaxZ;
|
||||
}
|
||||
|
||||
if (iterationCount++ > 16) {
|
||||
break;
|
||||
}
|
||||
|
||||
Optional<Vector3d> collisionPoint = BlockCollisions.clip(currentX, currentY, currentZ, currentX + 1, currentY + 1, currentZ + 1, start, end);
|
||||
if (!collisionPoint.isEmpty()) {
|
||||
Vector3d collisionVec = collisionPoint.get();
|
||||
double clampedX = GrimMath.clamp(collisionVec.x, currentX + 1.0E-5F, currentX + 1.0 - 1.0E-5F);
|
||||
double clampedY = GrimMath.clamp(collisionVec.y, currentY + 1.0E-5F, currentY + 1.0 - 1.0E-5F);
|
||||
double clampedZ = GrimMath.clamp(collisionVec.z, currentZ + 1.0E-5F, currentZ + 1.0 - 1.0E-5F);
|
||||
int endX = GrimMath.floor(clampedX + boundingBox.getXSize());
|
||||
int endY = GrimMath.floor(clampedY + boundingBox.getYSize());
|
||||
int endZ = GrimMath.floor(clampedZ + boundingBox.getZSize());
|
||||
|
||||
for (int x = currentX; x <= endX; x++) {
|
||||
for (int y = currentY; y <= endY; y++) {
|
||||
for (int z = currentZ; z <= endZ; z++) {
|
||||
output.add(new Vector3i(x, y, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package ac.grim.grimac.predictionengine.blockeffects.impl;
|
||||
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.predictionengine.blockeffects.BlockEffectsResolver;
|
||||
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
|
||||
import ac.grim.grimac.utils.math.GrimMath;
|
||||
import ac.grim.grimac.utils.nmsutil.Collisions;
|
||||
import ac.grim.grimac.utils.nmsutil.GetBoundingBox;
|
||||
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
|
||||
import com.github.retrooper.packetevents.protocol.world.states.type.StateType;
|
||||
import com.github.retrooper.packetevents.util.Vector3d;
|
||||
import com.github.retrooper.packetevents.util.Vector3i;
|
||||
import it.unimi.dsi.fastutil.longs.LongSet;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
// 1.21.4
|
||||
public class BlockEffectsResolverV1_21_4 implements BlockEffectsResolver {
|
||||
|
||||
public static final BlockEffectsResolver INSTANCE = new BlockEffectsResolverV1_21_4();
|
||||
|
||||
@Override
|
||||
public void applyEffectsFromBlocks(GrimPlayer player) {
|
||||
LongSet visitedBlocks = player.visitedBlocks;
|
||||
|
||||
for (GrimPlayer.Movement movement : player.finalMovementsThisTick) {
|
||||
Vector3d from = movement.from();
|
||||
Vector3d to = movement.to();
|
||||
|
||||
SimpleCollisionBox boundingBox = GetBoundingBox.getCollisionBoxForPlayer(player, to.x, to.y, to.z).expand(-1.0E-5F);
|
||||
for (Vector3i blockPos : boxTraverseBlocks(from, to, boundingBox)) {
|
||||
WrappedBlockState blockState = player.compensatedWorld.getBlock(blockPos);
|
||||
StateType blockType = blockState.getType();
|
||||
|
||||
if (blockType.isAir()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (visitedBlocks.add(GrimMath.asLong(blockPos))) {
|
||||
Collisions.onInsideBlock(player, blockType, blockState, blockPos.x, blockPos.y, blockPos.z, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visitedBlocks.clear();
|
||||
}
|
||||
|
||||
private static Iterable<Vector3i> boxTraverseBlocks(Vector3d start, Vector3d end, SimpleCollisionBox boundingBox) {
|
||||
Vector3d direction = end.subtract(start);
|
||||
Iterable<Vector3i> initialBlocks = SimpleCollisionBox.betweenClosed(boundingBox);
|
||||
if (direction.lengthSquared() < GrimMath.square(0.99999F)) {
|
||||
return initialBlocks;
|
||||
} else {
|
||||
Set<Vector3i> traversedBlocks = new ObjectLinkedOpenHashSet<>();
|
||||
Vector3d boxMinPosition = boundingBox.min().toVector3d();
|
||||
Vector3d subtractedMinPosition = boxMinPosition.subtract(direction);
|
||||
BlockEffectsResolverV1_21_2.addCollisionsAlongTravel(traversedBlocks, subtractedMinPosition, boxMinPosition, boundingBox);
|
||||
|
||||
for (Vector3i blockPos : initialBlocks) {
|
||||
traversedBlocks.add(blockPos);
|
||||
}
|
||||
|
||||
return traversedBlocks;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
package ac.grim.grimac.predictionengine.blockeffects.impl;
|
||||
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.predictionengine.blockeffects.BlockCollisions;
|
||||
import ac.grim.grimac.predictionengine.blockeffects.BlockEffectsResolver;
|
||||
import ac.grim.grimac.predictionengine.blockeffects.BlockStepVisitor;
|
||||
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
|
||||
import ac.grim.grimac.utils.math.GrimMath;
|
||||
import ac.grim.grimac.utils.nmsutil.Collisions;
|
||||
import ac.grim.grimac.utils.nmsutil.GetBoundingBox;
|
||||
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
|
||||
import com.github.retrooper.packetevents.protocol.world.states.type.StateType;
|
||||
import com.github.retrooper.packetevents.util.Vector3d;
|
||||
import com.github.retrooper.packetevents.util.Vector3i;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.longs.LongSet;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
// 1.21.5
|
||||
public class BlockEffectsResolverV1_21_5 implements BlockEffectsResolver {
|
||||
|
||||
public static final BlockEffectsResolver INSTANCE = new BlockEffectsResolverV1_21_5();
|
||||
|
||||
@Override
|
||||
public void applyEffectsFromBlocks(GrimPlayer player) {
|
||||
LongSet visitedBlocks = player.visitedBlocks;
|
||||
|
||||
for (GrimPlayer.Movement movement : player.finalMovementsThisTick) {
|
||||
Vector3d from = movement.from();
|
||||
Vector3d to = movement.to().subtract(movement.from());
|
||||
if (movement.axisIndependant() && to.lengthSquared() > 0.0) {
|
||||
for (Collisions.Axis axis : BlockCollisions.axisStepOrder(to)) {
|
||||
double value = axis.get(to);
|
||||
if (value != 0.0) {
|
||||
Vector3d vector = BlockCollisions.relative(from, axis.getPositive(), value);
|
||||
checkInsideBlocks(player, from, vector, visitedBlocks);
|
||||
from = vector;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
checkInsideBlocks(player, movement.from(), movement.to(), visitedBlocks);
|
||||
}
|
||||
}
|
||||
|
||||
visitedBlocks.clear();
|
||||
}
|
||||
|
||||
private static void checkInsideBlocks(GrimPlayer player, Vector3d from, Vector3d to, LongSet visitedBlocks) {
|
||||
SimpleCollisionBox boundingBox = GetBoundingBox.getCollisionBoxForPlayer(player, to.x, to.y, to.z).expand(-1.0E-5F);
|
||||
forEachBlockIntersectedBetween(from, to, boundingBox, (blockPos, i) -> {
|
||||
WrappedBlockState blockState = player.compensatedWorld.getBlock(blockPos);
|
||||
StateType blockType = blockState.getType();
|
||||
|
||||
if (blockType.isAir()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (visitedBlocks.add(GrimMath.asLong(blockPos))) {
|
||||
Collisions.onInsideBlock(player, blockType, blockState, blockPos.x, blockPos.y, blockPos.z, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private static void forEachBlockIntersectedBetween(Vector3d start, Vector3d end, SimpleCollisionBox boundingBox, BlockStepVisitor blockStepVisitor) {
|
||||
Vector3d direction = end.subtract(start);
|
||||
if (!(direction.lengthSquared() < GrimMath.square(0.99999F))) {
|
||||
LongSet alreadyVisited = new LongOpenHashSet();
|
||||
Vector3d boxMinPosition = boundingBox.min().toVector3d();
|
||||
Vector3d subtractedMinPosition = boxMinPosition.subtract(direction);
|
||||
int iterationCount = addCollisionsAlongTravel(alreadyVisited, subtractedMinPosition, boxMinPosition, boundingBox, blockStepVisitor);
|
||||
|
||||
for (Vector3i blockPos : SimpleCollisionBox.betweenClosed(boundingBox)) {
|
||||
if (!alreadyVisited.contains(GrimMath.asLong(blockPos))) {
|
||||
blockStepVisitor.visit(blockPos, iterationCount + 1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (Vector3i blockPos : SimpleCollisionBox.betweenClosed(boundingBox)) {
|
||||
blockStepVisitor.visit(blockPos, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int addCollisionsAlongTravel(LongSet alreadyVisited, Vector3d start, Vector3d end, SimpleCollisionBox boundingBox, BlockStepVisitor blockStepVisitor) {
|
||||
Vector3d direction = end.subtract(start);
|
||||
int currentX = GrimMath.floor(start.x);
|
||||
int currentY = GrimMath.floor(start.y);
|
||||
int currentZ = GrimMath.floor(start.z);
|
||||
int stepX = GrimMath.sign(direction.x);
|
||||
int stepY = GrimMath.sign(direction.y);
|
||||
int stepZ = GrimMath.sign(direction.z);
|
||||
double tMaxX = stepX == 0 ? Double.MAX_VALUE : stepX / direction.x;
|
||||
double tMaxY = stepY == 0 ? Double.MAX_VALUE : stepY / direction.y;
|
||||
double tMaxZ = stepZ == 0 ? Double.MAX_VALUE : stepZ / direction.z;
|
||||
double tDeltaX = tMaxX * (stepX > 0 ? 1.0 - GrimMath.frac(start.x) : GrimMath.frac(start.x));
|
||||
double tDeltaY = tMaxY * (stepY > 0 ? 1.0 - GrimMath.frac(start.y) : GrimMath.frac(start.y));
|
||||
double tDeltaZ = tMaxZ * (stepZ > 0 ? 1.0 - GrimMath.frac(start.z) : GrimMath.frac(start.z));
|
||||
int iterationCount = 0;
|
||||
|
||||
while (tDeltaX <= 1.0 || tDeltaY <= 1.0 || tDeltaZ <= 1.0) {
|
||||
if (tDeltaX < tDeltaY) {
|
||||
if (tDeltaX < tDeltaZ) {
|
||||
currentX += stepX;
|
||||
tDeltaX += tMaxX;
|
||||
} else {
|
||||
currentZ += stepZ;
|
||||
tDeltaZ += tMaxZ;
|
||||
}
|
||||
} else if (tDeltaY < tDeltaZ) {
|
||||
currentY += stepY;
|
||||
tDeltaY += tMaxY;
|
||||
} else {
|
||||
currentZ += stepZ;
|
||||
tDeltaZ += tMaxZ;
|
||||
}
|
||||
|
||||
if (iterationCount++ > 16) {
|
||||
break;
|
||||
}
|
||||
|
||||
Optional<Vector3d> collisionPoint = BlockCollisions.clip(currentX, currentY, currentZ, currentX + 1, currentY + 1, currentZ + 1, start, end);
|
||||
if (!collisionPoint.isEmpty()) {
|
||||
Vector3d collisionVec = collisionPoint.get();
|
||||
double clampedX = GrimMath.clamp(collisionVec.x, currentX + 1.0E-5F, currentX + 1.0 - 1.0E-5F);
|
||||
double clampedY = GrimMath.clamp(collisionVec.y, currentY + 1.0E-5F, currentY + 1.0 - 1.0E-5F);
|
||||
double clampedZ = GrimMath.clamp(collisionVec.z, currentZ + 1.0E-5F, currentZ + 1.0 - 1.0E-5F);
|
||||
int endX = GrimMath.floor(clampedX + boundingBox.getXSize());
|
||||
int endY = GrimMath.floor(clampedY + boundingBox.getYSize());
|
||||
int endZ = GrimMath.floor(clampedZ + boundingBox.getZSize());
|
||||
|
||||
for (int x = currentX; x <= endX; x++) {
|
||||
for (int y = currentY; y <= endY; y++) {
|
||||
for (int z = currentZ; z <= endZ; z++) {
|
||||
if (alreadyVisited.add(GrimMath.asLong(x, y, z))) {
|
||||
blockStepVisitor.visit(new Vector3i(x, y, z), iterationCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return iterationCount;
|
||||
}
|
||||
|
||||
}
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
package ac.grim.grimac.predictionengine.blockeffects.impl;
|
||||
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.predictionengine.blockeffects.BlockCollisions;
|
||||
import ac.grim.grimac.predictionengine.blockeffects.BlockEffectsResolver;
|
||||
import ac.grim.grimac.predictionengine.blockeffects.BlockStepVisitor;
|
||||
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
|
||||
import ac.grim.grimac.utils.math.GrimMath;
|
||||
import ac.grim.grimac.utils.nmsutil.Collisions;
|
||||
import ac.grim.grimac.utils.nmsutil.GetBoundingBox;
|
||||
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
|
||||
import com.github.retrooper.packetevents.protocol.world.states.type.StateType;
|
||||
import com.github.retrooper.packetevents.util.Vector3d;
|
||||
import com.github.retrooper.packetevents.util.Vector3i;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.longs.LongSet;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
// 1.21.6-1.21.8
|
||||
public class BlockEffectsResolverV1_21_6 implements BlockEffectsResolver {
|
||||
|
||||
public static final BlockEffectsResolver INSTANCE = new BlockEffectsResolverV1_21_6();
|
||||
|
||||
@Override
|
||||
public void applyEffectsFromBlocks(GrimPlayer player) {
|
||||
LongSet visitedBlocks = player.visitedBlocks;
|
||||
|
||||
for (GrimPlayer.Movement movement : player.finalMovementsThisTick) {
|
||||
Vector3d from = movement.from();
|
||||
Vector3d to = movement.to().subtract(movement.from());
|
||||
if (movement.axisIndependant() && to.lengthSquared() > 0.0) {
|
||||
for (Collisions.Axis axis : BlockCollisions.axisStepOrder(to)) {
|
||||
double value = axis.get(to);
|
||||
if (value != 0.0) {
|
||||
Vector3d vector = BlockCollisions.relative(from, axis.getPositive(), value);
|
||||
checkInsideBlocks(player, from, vector, visitedBlocks);
|
||||
from = vector;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
checkInsideBlocks(player, movement.from(), movement.to(), visitedBlocks);
|
||||
}
|
||||
}
|
||||
|
||||
visitedBlocks.clear();
|
||||
}
|
||||
|
||||
private static void checkInsideBlocks(GrimPlayer player, Vector3d from, Vector3d to, LongSet visitedBlocks) {
|
||||
SimpleCollisionBox boundingBox = GetBoundingBox.getCollisionBoxForPlayer(player, to.x, to.y, to.z).expand(-1.0E-5F);
|
||||
forEachBlockIntersectedBetween(from, to, boundingBox, (blockPos, i) -> {
|
||||
WrappedBlockState blockState = player.compensatedWorld.getBlock(blockPos);
|
||||
StateType blockType = blockState.getType();
|
||||
|
||||
if (blockType.isAir()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (visitedBlocks.add(GrimMath.asLong(blockPos))) {
|
||||
Collisions.onInsideBlock(player, blockType, blockState, blockPos.x, blockPos.y, blockPos.z, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private static boolean forEachBlockIntersectedBetween(Vector3d start, Vector3d end, SimpleCollisionBox boundingBox, BlockStepVisitor blockStepVisitor) {
|
||||
Vector3d direction = end.subtract(start);
|
||||
if (direction.lengthSquared() < GrimMath.square(0.99999F)) {
|
||||
for (Vector3i blockPos : SimpleCollisionBox.betweenClosed(boundingBox)) {
|
||||
if (!blockStepVisitor.visit(blockPos, 0)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
LongSet alreadyVisited = new LongOpenHashSet();
|
||||
Vector3d boxMinPosition = boundingBox.min().toVector3d();
|
||||
Vector3d subtractedMinPosition = boxMinPosition.subtract(direction);
|
||||
|
||||
int iterationCount = addCollisionsAlongTravel(alreadyVisited, subtractedMinPosition, boxMinPosition, boundingBox, blockStepVisitor);
|
||||
if (iterationCount < 0) {
|
||||
return false;
|
||||
} else {
|
||||
for (Vector3i blockPos : SimpleCollisionBox.betweenClosed(boundingBox)) {
|
||||
if (!alreadyVisited.contains(GrimMath.asLong(blockPos)) && !blockStepVisitor.visit(blockPos, iterationCount + 1)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int addCollisionsAlongTravel(LongSet alreadyVisited, Vector3d start, Vector3d end, SimpleCollisionBox boundingBox, BlockStepVisitor blockStepVisitor) {
|
||||
Vector3d direction = end.subtract(start);
|
||||
int currentX = GrimMath.floor(start.x);
|
||||
int currentY = GrimMath.floor(start.y);
|
||||
int currentZ = GrimMath.floor(start.z);
|
||||
int stepX = GrimMath.sign(direction.x);
|
||||
int stepY = GrimMath.sign(direction.y);
|
||||
int stepZ = GrimMath.sign(direction.z);
|
||||
double tMaxX = stepX == 0 ? Double.MAX_VALUE : stepX / direction.x;
|
||||
double tMaxY = stepY == 0 ? Double.MAX_VALUE : stepY / direction.y;
|
||||
double tMaxZ = stepZ == 0 ? Double.MAX_VALUE : stepZ / direction.z;
|
||||
double tDeltaX = tMaxX * (stepX > 0 ? 1.0 - GrimMath.frac(start.x) : GrimMath.frac(start.x));
|
||||
double tDeltaY = tMaxY * (stepY > 0 ? 1.0 - GrimMath.frac(start.y) : GrimMath.frac(start.y));
|
||||
double tDeltaZ = tMaxZ * (stepZ > 0 ? 1.0 - GrimMath.frac(start.z) : GrimMath.frac(start.z));
|
||||
int iterationCount = 0;
|
||||
|
||||
while (tDeltaX <= 1.0 || tDeltaY <= 1.0 || tDeltaZ <= 1.0) {
|
||||
if (tDeltaX < tDeltaY) {
|
||||
if (tDeltaX < tDeltaZ) {
|
||||
currentX += stepX;
|
||||
tDeltaX += tMaxX;
|
||||
} else {
|
||||
currentZ += stepZ;
|
||||
tDeltaZ += tMaxZ;
|
||||
}
|
||||
} else if (tDeltaY < tDeltaZ) {
|
||||
currentY += stepY;
|
||||
tDeltaY += tMaxY;
|
||||
} else {
|
||||
currentZ += stepZ;
|
||||
tDeltaZ += tMaxZ;
|
||||
}
|
||||
|
||||
if (iterationCount++ > 16) {
|
||||
break;
|
||||
}
|
||||
|
||||
Optional<Vector3d> collisionPoint = BlockCollisions.clip(currentX, currentY, currentZ, currentX + 1, currentY + 1, currentZ + 1, start, end);
|
||||
if (!collisionPoint.isEmpty()) {
|
||||
Vector3d collisionVec = collisionPoint.get();
|
||||
double clampedX = GrimMath.clamp(collisionVec.x, currentX + 1.0E-5F, currentX + 1.0 - 1.0E-5F);
|
||||
double clampedY = GrimMath.clamp(collisionVec.y, currentY + 1.0E-5F, currentY + 1.0 - 1.0E-5F);
|
||||
double clampedZ = GrimMath.clamp(collisionVec.z, currentZ + 1.0E-5F, currentZ + 1.0 - 1.0E-5F);
|
||||
int endX = GrimMath.floor(clampedX + boundingBox.getXSize());
|
||||
int endY = GrimMath.floor(clampedY + boundingBox.getYSize());
|
||||
int endZ = GrimMath.floor(clampedZ + boundingBox.getZSize());
|
||||
|
||||
for (int x = currentX; x <= endX; x++) {
|
||||
for (int y = currentY; y <= endY; y++) {
|
||||
for (int z = currentZ; z <= endZ; z++) {
|
||||
if (alreadyVisited.add(GrimMath.asLong(x, y, z)) && !blockStepVisitor.visit(new Vector3i(x, y, z), iterationCount)) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return iterationCount;
|
||||
}
|
||||
|
||||
}
|
||||
+2
-39
@@ -226,7 +226,7 @@ public class MovementTicker {
|
||||
Vector3d from = new Vector3d(player.lastX, player.lastY, player.lastZ);
|
||||
Vector3d to = new Vector3d(player.x, player.y, player.z);
|
||||
|
||||
player.addMovementThisTick(new GrimPlayer.Movement(from, to, true));
|
||||
player.addMovementThisTick(new GrimPlayer.Movement(from, to, new Vector3d(inputVel.getX(), inputVel.getY(), inputVel.getZ())));
|
||||
}
|
||||
|
||||
// This is where vanilla moves the bounding box and sets it
|
||||
@@ -481,44 +481,7 @@ public class MovementTicker {
|
||||
}
|
||||
}
|
||||
|
||||
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_21_2)) {
|
||||
// Reset stuck speed so it can update
|
||||
if (player.stuckSpeedMultiplier.getX() < 0.99) {
|
||||
player.uncertaintyHandler.lastStuckSpeedMultiplier.reset();
|
||||
}
|
||||
|
||||
player.stuckSpeedMultiplier = new Vector3dm(1, 1, 1);
|
||||
player.finalMovementsThisTick.clear();
|
||||
|
||||
Vector3d from = new Vector3d(player.lastX, player.lastY, player.lastZ);
|
||||
Vector3d to = new Vector3d(player.x, player.y, player.z);
|
||||
|
||||
ClientVersion clientVersion = player.getClientVersion();
|
||||
if (clientVersion.isOlderThan(ClientVersion.V_1_21_5)) {
|
||||
player.finalMovementsThisTick.add(new GrimPlayer.Movement(from, to, false));
|
||||
} else if (clientVersion.isNewerThanOrEquals(ClientVersion.V_1_21_5)) {
|
||||
player.finalMovementsThisTick.addAll(player.movementThisTick);
|
||||
player.movementThisTick.clear();
|
||||
|
||||
if (player.finalMovementsThisTick.isEmpty()) {
|
||||
player.finalMovementsThisTick.add(new GrimPlayer.Movement(from, to, false));
|
||||
} else if (player.finalMovementsThisTick.get(player.finalMovementsThisTick.size() - 1).to().distanceSquared(to) > 9.9999994E-11F) {
|
||||
player.finalMovementsThisTick.add(new GrimPlayer.Movement(player.finalMovementsThisTick.get(player.finalMovementsThisTick.size() - 1).to(), to, false));
|
||||
}
|
||||
}
|
||||
|
||||
Collisions.applyEffectsFromBlocks(player);
|
||||
|
||||
if (player.stuckSpeedMultiplier.getX() < 0.9) {
|
||||
// Reset fall distance if stuck in block
|
||||
player.fallDistance = 0;
|
||||
}
|
||||
|
||||
// Flying players are not affected by cobwebs/sweet berry bushes
|
||||
if (player.isFlying) {
|
||||
player.stuckSpeedMultiplier = new Vector3dm(1, 1, 1);
|
||||
}
|
||||
}
|
||||
Collisions.applyEffectsFromBlocks(player);
|
||||
}
|
||||
|
||||
public boolean canStandOnLava() {
|
||||
|
||||
+15
-2
@@ -437,9 +437,21 @@ public class PredictionEngine {
|
||||
minimumMovement = 0.005D;
|
||||
}
|
||||
|
||||
for (VectorData vector : velocities) {
|
||||
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_21_5) && !player.inVehicle()) {
|
||||
boolean stupidVectors = player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_21_5) && !player.inVehicle();
|
||||
boolean stuckOnEdge = player.uncertaintyHandler.stuckOnEdge.hasOccurredSince(2);
|
||||
Set<VectorData> vectors = stupidVectors && stuckOnEdge ? new HashSet<>(velocities) : velocities;
|
||||
for (VectorData vector : vectors) {
|
||||
if (stupidVectors) {
|
||||
if (Collisions.getHorizontalDistanceSqr(vector.vector) < 9.0E-6) {
|
||||
if (stuckOnEdge) {
|
||||
VectorData edgeVector = vector.returnNewModified(vector.vector.clone(), vector.vectorType);
|
||||
if (Math.abs(edgeVector.vector.getY()) < minimumMovement) {
|
||||
edgeVector.vector.setY(0D);
|
||||
}
|
||||
|
||||
velocities.add(edgeVector);
|
||||
}
|
||||
|
||||
vector.vector.setX(0D);
|
||||
vector.vector.setZ(0D);
|
||||
}
|
||||
@@ -460,6 +472,7 @@ public class PredictionEngine {
|
||||
}
|
||||
|
||||
public void addExplosionToPossibilities(GrimPlayer player, Set<VectorData> existingVelocities) {
|
||||
if (player.likelyExplosions == null && player.firstBreadExplosion == null) return;
|
||||
for (VectorData vector : new HashSet<>(existingVelocities)) {
|
||||
if (player.likelyExplosions != null) {
|
||||
existingVelocities.add(new VectorData(vector.vector.clone().add(player.likelyExplosions.vector), vector, VectorData.VectorType.Explosion));
|
||||
|
||||
+4
@@ -186,6 +186,10 @@ public class PredictionEngineBoat extends PredictionEngine {
|
||||
public void endOfTick(GrimPlayer player, double d) {
|
||||
super.endOfTick(player, d);
|
||||
Collisions.handleInsideBlocks(player);
|
||||
|
||||
// yes, this is how mojank does it
|
||||
Collisions.applyEffectsFromBlocks(player);
|
||||
Collisions.applyEffectsFromBlocks(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -34,36 +34,105 @@ public class MessageUtil {
|
||||
return vec == null ? "null" : vec.x + ", " + vec.y + ", " + vec.z;
|
||||
}
|
||||
|
||||
public @NotNull String replacePlaceholders(@Nullable GrimPlayer player, @NotNull String string, boolean removeFormatting) {
|
||||
@Contract("_, null, _ -> null; _, !null, _ -> !null")
|
||||
public @Nullable String replacePlaceholders(@Nullable GrimPlayer player, @Nullable String string, boolean removeFormatting) {
|
||||
return replacePlaceholders(player, player == null ? null : player.platformPlayer, string, removeFormatting);
|
||||
}
|
||||
|
||||
public @NotNull String replacePlaceholders(@Nullable GrimPlayer player, @NotNull String string) {
|
||||
@Contract("_, null -> null; _, !null -> !null")
|
||||
public @Nullable String replacePlaceholders(@Nullable GrimPlayer player, @Nullable String string) {
|
||||
return replacePlaceholders(player, player == null ? null : player.platformPlayer, string, false);
|
||||
}
|
||||
|
||||
public @NotNull String replacePlaceholders(@Nullable Sender sender, @NotNull String string) {
|
||||
@Contract("_, null -> null; _, !null -> !null")
|
||||
public @Nullable String replacePlaceholders(@Nullable Sender sender, @Nullable String string) {
|
||||
return replacePlaceholders(sender != null ? sender.getPlatformPlayer() : null, string);
|
||||
}
|
||||
|
||||
public @NotNull String replacePlaceholders(@Nullable PlatformPlayer player, @NotNull String string) {
|
||||
@Contract("_, null -> null; _, !null -> !null")
|
||||
public @Nullable String replacePlaceholders(@Nullable PlatformPlayer player, @Nullable String string) {
|
||||
return replacePlaceholders(player == null ? null : GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(player.getUniqueId()), player, string, false);
|
||||
}
|
||||
|
||||
private @NotNull String replacePlaceholders(@Nullable GrimPlayer grimPlayer, @Nullable PlatformPlayer platformPlayer, @NotNull String string, boolean removeFormatting) {
|
||||
for (Map.Entry<String, String> entry : GrimAPI.INSTANCE.getExternalAPI().getStaticReplacements().entrySet()) {
|
||||
string = string.replace(entry.getKey(), entry.getValue());
|
||||
private static final Pattern UNIFIED_PLACEHOLDER_PATTERN = Pattern.compile("%([a-zA-Z0-9_]+)%");
|
||||
|
||||
@Contract("_, _, null, _ -> null; _, _, !null, _ -> !null")
|
||||
private @Nullable String replacePlaceholders(@Nullable GrimPlayer grimPlayer, @Nullable PlatformPlayer platformPlayer, @Nullable String string, boolean removeFormatting) {
|
||||
if (string == null) return null;
|
||||
|
||||
// --- OPTIMIZATION 1: THE FAST PATH ---
|
||||
// If the string contains no '%' characters, it's impossible for it to have placeholders.
|
||||
// indexOf() is a JVM intrinsic and is magnitudes faster than even creating a Matcher.
|
||||
if (string.indexOf('%') == -1) {
|
||||
// Since there are no % signs we can skip calling papi or our own replacement code
|
||||
return string;
|
||||
}
|
||||
|
||||
if (grimPlayer != null) {
|
||||
for (Map.Entry<String, Function<GrimUser, String>> entry : GrimAPI.INSTANCE.getExternalAPI().getVariableReplacements().entrySet()) {
|
||||
String value = entry.getValue().apply(grimPlayer).replace('%', PLACEHOLDER_ESCAPE_CHAR);
|
||||
if (removeFormatting) value = filterDiscordText(value);
|
||||
string = string.replace(entry.getKey(), value);
|
||||
final Matcher matcher = UNIFIED_PLACEHOLDER_PATTERN.matcher(string);
|
||||
|
||||
// If matcher.find() is false, it means '%' existed but not in a valid %...% pattern.
|
||||
// This avoids allocating a StringBuilder unless absolutely necessary.
|
||||
if (!matcher.find()) {
|
||||
return GrimAPI.INSTANCE.getMessagePlaceHolderManager().replacePlaceholders(platformPlayer, string);
|
||||
}
|
||||
|
||||
// Get references to the maps once, outside the loop.
|
||||
final Map<String, String> staticReplacements = GrimAPI.INSTANCE.getExternalAPI().getStaticReplacements();
|
||||
final Map<String, Function<GrimUser, String>> variableReplacements = GrimAPI.INSTANCE.getExternalAPI().getVariableReplacements();
|
||||
final StringBuilder sb = new StringBuilder(string.length() + 32); // Pre-size with a little extra room
|
||||
|
||||
// --- OPTIMIZATION 2: THE UNIFIED SINGLE-PASS LOOP ---
|
||||
// We use a do-while loop because we already performed the first matcher.find().
|
||||
do {
|
||||
// The full placeholder, e.g., "%tps%" or "%prefix%"
|
||||
final String keyWithPercent = matcher.group(0);
|
||||
String value = null;
|
||||
|
||||
// --- OPTIMIZATION 3: UNIFIED LOOKUP ---
|
||||
// We check the static map first. This is a single, O(1) hash map lookup.
|
||||
String staticValue = staticReplacements.get(keyWithPercent);
|
||||
|
||||
if (staticValue != null) {
|
||||
value = staticValue;
|
||||
} else if (grimPlayer != null) {
|
||||
// If it's not a static placeholder, check if it's a dynamic one.
|
||||
// This is a second, O(1) hash map lookup.
|
||||
final Function<GrimUser, String> func = variableReplacements.get(keyWithPercent);
|
||||
if (func != null) {
|
||||
// LAZY EVALUATION: We only call the expensive function (like getTPS)
|
||||
// if we actually found its placeholder in the string.
|
||||
value = func.apply(grimPlayer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return GrimAPI.INSTANCE.getMessagePlaceHolderManager().replacePlaceholders(platformPlayer, string).replace(PLACEHOLDER_ESCAPE_CHAR, '%');
|
||||
// If we found no replacement, `value` will be null.
|
||||
// In that case, we treat the placeholder as literal text by appending the original key.
|
||||
if (value == null) {
|
||||
value = keyWithPercent;
|
||||
}
|
||||
|
||||
// --- OPTIMIZATION 4: CONDITIONAL FORMATTING ---
|
||||
// The check for `removeFormatting` is inside the loop, but it's a simple boolean
|
||||
// check and the cost is negligible compared to the string operations.
|
||||
if (removeFormatting) {
|
||||
// Note: This assumes `filterDiscordText` is reasonably fast.
|
||||
// If it's slow, there are further micro-optimizations, but this is the right place for it.
|
||||
value = filterDiscordText(value);
|
||||
}
|
||||
|
||||
// `appendReplacement` efficiently appends the text between matches and our replacement value.
|
||||
// `Matcher.quoteReplacement` should handle any '$' or '\' in the replacement value.
|
||||
matcher.appendReplacement(sb, Matcher.quoteReplacement(value));
|
||||
|
||||
} while (matcher.find());
|
||||
|
||||
// Append the final part of the string after the last found placeholder.
|
||||
matcher.appendTail(sb);
|
||||
|
||||
// Create the final string from our builder.
|
||||
String grimReplaced = sb.toString();
|
||||
|
||||
return GrimAPI.INSTANCE.getMessagePlaceHolderManager().replacePlaceholders(platformPlayer, grimReplaced).replace(PLACEHOLDER_ESCAPE_CHAR, '%');
|
||||
}
|
||||
|
||||
public static String filterDiscordText(String message) {
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
|
||||
@@ -555,6 +555,7 @@ public class BlockPlace {
|
||||
// 1.8 clients will simply not send the place when it fails, thanks mojang.
|
||||
if (player.getClientVersion().isNewerThan(ClientVersion.V_1_8)) {
|
||||
for (PacketEntity entity : player.compensatedEntities.entityMap.values()) {
|
||||
if (!entity.canHit()) continue;
|
||||
SimpleCollisionBox interpBox = entity.getPossibleCollisionBoxes();
|
||||
|
||||
final double scale = entity.getAttributeValue(Attributes.SCALE);
|
||||
@@ -591,7 +592,7 @@ public class BlockPlace {
|
||||
|
||||
// Check for waterlogged
|
||||
if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_13)) {
|
||||
if (state.getInternalData().containsKey(StateValue.WATERLOGGED)) { // waterloggable
|
||||
if (state.hasProperty(StateValue.WATERLOGGED)) { // waterloggable
|
||||
state.setWaterlogged(existingState.getType() == StateTypes.WATER && existingState.getLevel() == 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,7 +318,7 @@ public enum BlockPlaceResult {
|
||||
WrappedBlockState typePlacingOn = place.getDirectionalState(primaryDir.getOppositeFace()); // Block we are placing on
|
||||
|
||||
// Check to see if we can place on the block or there is dripstone on the block that we are placing on also pointing upwards
|
||||
boolean primarySameType = typePlacingOn.getInternalData().containsKey(StateValue.VERTICAL_DIRECTION) && typePlacingOn.getVerticalDirection().name().equals(primaryDir.name());
|
||||
boolean primarySameType = typePlacingOn.hasProperty(StateValue.VERTICAL_DIRECTION) && typePlacingOn.getVerticalDirection().name().equals(primaryDir.name());
|
||||
boolean primaryValid = place.isFullFace(primaryDir.getOppositeFace()) || primarySameType;
|
||||
|
||||
// Try to use the opposite direction, just to see if switching directions makes it valid.
|
||||
@@ -326,7 +326,7 @@ public enum BlockPlaceResult {
|
||||
BlockFace secondaryDirection = primaryDir.getOppositeFace(); // See if placing it DOWNWARDS is valid
|
||||
WrappedBlockState secondaryType = place.getDirectionalState(secondaryDirection.getOppositeFace()); // Get the block above us
|
||||
// Check if the dripstone above us is also facing downwards
|
||||
boolean secondarySameType = secondaryType.getInternalData().containsKey(StateValue.VERTICAL_DIRECTION) && secondaryType.getVerticalDirection().name().equals(primaryDir.name());
|
||||
boolean secondarySameType = secondaryType.hasProperty(StateValue.VERTICAL_DIRECTION) && secondaryType.getVerticalDirection().name().equals(primaryDir.name());
|
||||
|
||||
primaryDir = secondaryDirection;
|
||||
// Update block survivability
|
||||
|
||||
@@ -378,7 +378,7 @@ public enum CollisionData implements CollisionFactory {
|
||||
new HexCollisionBox(5.0D, 0.0D, 5.0D, 11.0D, 7.0D, 11.0D),
|
||||
new HexCollisionBox(6.0D, 7.0D, 6.0D, 10.0D, 9.0D, 10.0D));
|
||||
|
||||
}, StateTypes.LANTERN, StateTypes.SOUL_LANTERN),
|
||||
}, BlockTags.LANTERNS.getStates().toArray(new StateType[0])),
|
||||
|
||||
|
||||
LECTERN((player, version, data, x, y, z) -> {
|
||||
@@ -516,7 +516,7 @@ public enum CollisionData implements CollisionFactory {
|
||||
}
|
||||
|
||||
return new HexCollisionBox(6.5D, 6.5D, 0.0D, 9.5D, 9.5D, 16.0D);
|
||||
}, StateTypes.CHAIN),
|
||||
}, Materials.getChains().toArray(new StateType[0])),
|
||||
|
||||
CHORUS_PLANT(new DynamicChorusPlant(), StateTypes.CHORUS_PLANT),
|
||||
|
||||
@@ -552,7 +552,7 @@ public enum CollisionData implements CollisionFactory {
|
||||
|
||||
STAIR(new DynamicStair(), BlockTags.STAIRS.getStates().toArray(new StateType[0])),
|
||||
|
||||
CHEST(new DynamicChest(), StateTypes.CHEST, StateTypes.TRAPPED_CHEST),
|
||||
CHEST(new DynamicChest(), Materials.getChests().toArray(new StateType[0])),
|
||||
|
||||
ENDER_CHEST(new SimpleCollisionBox(0.0625F, 0.0F, 0.0625F,
|
||||
0.9375F, 0.875F, 0.9375F, false),
|
||||
@@ -675,7 +675,7 @@ public enum CollisionData implements CollisionFactory {
|
||||
0.625, 0.625, 0.625, false),
|
||||
StateTypes.STRUCTURE_VOID),
|
||||
|
||||
END_ROD((player, version, data, x, y, z) -> getEndRod(version, data.getFacing()), StateTypes.END_ROD, StateTypes.LIGHTNING_ROD),
|
||||
END_ROD((player, version, data, x, y, z) -> getEndRod(version, data.getFacing()), Materials.getRods().toArray(new StateType[0])),
|
||||
|
||||
CAULDRON((player, version, data, x, y, z) -> {
|
||||
if (version.isNewerThan(ClientVersion.V_1_13_2)) { // changed in 19w13a, 1.14 Snapshot
|
||||
@@ -813,7 +813,7 @@ public enum CollisionData implements CollisionFactory {
|
||||
}, StateTypes.TRIPWIRE_HOOK),
|
||||
|
||||
TORCH(new HexCollisionBox(6.0D, 0.0D, 6.0D, 10.0D, 10.0D, 10.0D),
|
||||
StateTypes.TORCH, StateTypes.REDSTONE_TORCH),
|
||||
StateTypes.TORCH, StateTypes.REDSTONE_TORCH, StateTypes.COPPER_TORCH),
|
||||
|
||||
WALL_TORCH((player, version, data, x, y, z) -> switch (data.getFacing()) {
|
||||
case NORTH -> new HexCollisionBox(5.5D, 3.0D, 11.0D, 10.5D, 13.0D, 16.0D);
|
||||
@@ -822,7 +822,7 @@ public enum CollisionData implements CollisionFactory {
|
||||
case EAST -> new HexCollisionBox(0.0D, 3.0D, 5.5D, 5.0D, 13.0D, 10.5D);
|
||||
// 1.13 separates wall and normal torches, 1.12 does not
|
||||
default -> new HexCollisionBox(6.0D, 0.0D, 6.0D, 10.0D, 10.0D, 10.0D);
|
||||
}, StateTypes.WALL_TORCH, StateTypes.REDSTONE_WALL_TORCH),
|
||||
}, StateTypes.WALL_TORCH, StateTypes.REDSTONE_WALL_TORCH, StateTypes.COPPER_WALL_TORCH),
|
||||
|
||||
// 1.17 blocks
|
||||
CANDLE((player, version, data, x, y, z) -> {
|
||||
|
||||
@@ -331,7 +331,7 @@ public enum HitboxData implements HitBoxFactory {
|
||||
if (data.getSouth() == South.TRUE) boxes.add(new HexCollisionBox(0, 0, 15, 16, 16, 16));
|
||||
|
||||
// TODO: when was isUp() added?
|
||||
if (data.getInternalData().containsKey(StateValue.UP) && data.isUp()) {
|
||||
if (data.hasProperty(StateValue.UP) && data.isUp()) {
|
||||
boxes.add(new HexCollisionBox(0, 15, 0, 16, 16, 16));
|
||||
}
|
||||
|
||||
|
||||
+127
@@ -1,10 +1,13 @@
|
||||
package ac.grim.grimac.utils.collisions.datatypes;
|
||||
|
||||
import ac.grim.grimac.predictionengine.blockeffects.BlockCollisions;
|
||||
import ac.grim.grimac.utils.math.GrimMath;
|
||||
import ac.grim.grimac.utils.math.Location;
|
||||
import ac.grim.grimac.utils.math.Vector3dm;
|
||||
import ac.grim.grimac.utils.nmsutil.Collisions;
|
||||
import ac.grim.grimac.utils.nmsutil.Ray;
|
||||
import com.github.retrooper.packetevents.protocol.world.BlockFace;
|
||||
import com.github.retrooper.packetevents.protocol.world.Direction;
|
||||
import com.github.retrooper.packetevents.util.Vector3d;
|
||||
import com.github.retrooper.packetevents.util.Vector3i;
|
||||
import com.google.common.collect.AbstractIterator;
|
||||
@@ -448,6 +451,10 @@ public class SimpleCollisionBox implements CollisionBox {
|
||||
return new Vector3dm(minX, minY, minZ);
|
||||
}
|
||||
|
||||
public Vector3d getCenter() {
|
||||
return new Vector3d(GrimMath.lerp(0.5, this.minX, this.maxX), GrimMath.lerp(0.5, this.minY, this.maxY), GrimMath.lerp(0.5, this.minZ, this.maxZ));
|
||||
}
|
||||
|
||||
public DoubleList getYPointPositions() {
|
||||
return create(minX, minY, minZ, maxX, maxY, maxZ);
|
||||
}
|
||||
@@ -509,6 +516,37 @@ public class SimpleCollisionBox implements CollisionBox {
|
||||
return maxZ - minZ;
|
||||
}
|
||||
|
||||
public SimpleCollisionBox move(Vector3d vector) {
|
||||
return this.move(vector.x, vector.y, vector.z);
|
||||
}
|
||||
|
||||
public SimpleCollisionBox move(double x, double y, double z) {
|
||||
return new SimpleCollisionBox(this.minX + x, this.minY + y, this.minZ + z, this.maxX + x, this.maxY + y, this.maxZ + z);
|
||||
}
|
||||
|
||||
public boolean intersects(SimpleCollisionBox collisionBox) {
|
||||
return this.intersects(collisionBox.minX, collisionBox.minY, collisionBox.minZ, collisionBox.maxX, collisionBox.maxY, collisionBox.maxZ);
|
||||
}
|
||||
|
||||
public boolean intersects(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
|
||||
return this.minX < maxX && this.maxX > minX && this.minY < maxY && this.maxY > minY && this.minZ < maxZ && this.maxZ > minZ;
|
||||
}
|
||||
|
||||
public boolean intersects(Vector3d min, Vector3d max) {
|
||||
return this.intersects(
|
||||
Math.min(min.x, max.x),
|
||||
Math.min(min.y, max.y),
|
||||
Math.min(min.z, max.z),
|
||||
Math.max(min.x, max.x),
|
||||
Math.max(min.y, max.y),
|
||||
Math.max(min.z, max.z)
|
||||
);
|
||||
}
|
||||
|
||||
public boolean intersects(Vector3i blockPos) {
|
||||
return this.intersects(blockPos.getX(), blockPos.getY(), blockPos.getZ(), blockPos.getX() + 1, blockPos.getY() + 1, blockPos.getZ() + 1);
|
||||
}
|
||||
|
||||
public static Iterable<Vector3i> betweenClosed(SimpleCollisionBox box) {
|
||||
Vector3i startBlockPos = containing(box.minX, box.minY, box.minZ);
|
||||
Vector3i endBlockPos = containing(box.maxX, box.maxY, box.maxZ);
|
||||
@@ -554,6 +592,95 @@ public class SimpleCollisionBox implements CollisionBox {
|
||||
};
|
||||
}
|
||||
|
||||
public static Iterable<Vector3i> betweenCornersInDirection(SimpleCollisionBox boundingBox, Vector3d directionVector) {
|
||||
Vector3d min = boundingBox.min().toVector3d();
|
||||
int minX = GrimMath.floor(min.x);
|
||||
int minY = GrimMath.floor(min.y);
|
||||
int minZ = GrimMath.floor(min.z);
|
||||
Vector3d max = boundingBox.max().toVector3d();
|
||||
int maxX = GrimMath.floor(max.x);
|
||||
int maxY = GrimMath.floor(max.y);
|
||||
int maxZ = GrimMath.floor(max.z);
|
||||
return betweenCornersInDirection(minX, minY, minZ, maxX, maxY, maxZ, directionVector);
|
||||
}
|
||||
|
||||
public static Iterable<Vector3i> betweenCornersInDirection(Vector3i min, Vector3i max, Vector3d directionVector) {
|
||||
return betweenCornersInDirection(min.getX(), min.getY(), min.getZ(), max.getX(), max.getY(), max.getZ(), directionVector);
|
||||
}
|
||||
|
||||
public static Iterable<Vector3i> betweenCornersInDirection(int x1, int y1, int z1, int x2, int y2, int z2, Vector3d directionVector) {
|
||||
int minX = Math.min(x1, x2);
|
||||
int minY = Math.min(y1, y2);
|
||||
int minZ = Math.min(z1, z2);
|
||||
int maxX = Math.max(x1, x2);
|
||||
int maxY = Math.max(y1, y2);
|
||||
int maxZ = Math.max(z1, z2);
|
||||
|
||||
int sizeX = maxX - minX;
|
||||
int sizeY = maxY - minY;
|
||||
int sizeZ = maxZ - minZ;
|
||||
|
||||
int startX = directionVector.x >= 0.0 ? minX : maxX;
|
||||
int startY = directionVector.y >= 0.0 ? minY : maxY;
|
||||
int startZ = directionVector.z >= 0.0 ? minZ : maxZ;
|
||||
|
||||
List<Collisions.Axis> axisOrder = BlockCollisions.axisStepOrder(directionVector);
|
||||
Collisions.Axis primaryAxis = axisOrder.get(0);
|
||||
Collisions.Axis secondaryAxis = axisOrder.get(1);
|
||||
Collisions.Axis tertiaryAxis = axisOrder.get(2);
|
||||
|
||||
Direction primaryDirection = primaryAxis.get(directionVector) >= 0.0 ? primaryAxis.getPositive() : primaryAxis.getNegative();
|
||||
Direction secondaryDirection = secondaryAxis.get(directionVector) >= 0.0 ? secondaryAxis.getPositive() : secondaryAxis.getNegative();
|
||||
Direction tertiaryDirection = tertiaryAxis.get(directionVector) >= 0.0 ? tertiaryAxis.getPositive() : tertiaryAxis.getNegative();
|
||||
|
||||
int primaryCount = primaryAxis.choose(sizeX, sizeY, sizeZ);
|
||||
int secondaryCount = secondaryAxis.choose(sizeX, sizeY, sizeZ);
|
||||
int tertiaryCount = tertiaryAxis.choose(sizeX, sizeY, sizeZ);
|
||||
|
||||
return () -> new AbstractIterator<>() {
|
||||
private int firstIndex;
|
||||
private int secondIndex;
|
||||
private int thirdIndex;
|
||||
private boolean end;
|
||||
private final int firstDirX = primaryDirection.getVector().getX();
|
||||
private final int firstDirY = primaryDirection.getVector().getY();
|
||||
private final int firstDirZ = primaryDirection.getVector().getZ();
|
||||
private final int secondDirX = secondaryDirection.getVector().getX();
|
||||
private final int secondDirY = secondaryDirection.getVector().getY();
|
||||
private final int secondDirZ = secondaryDirection.getVector().getZ();
|
||||
private final int thirdDirX = tertiaryDirection.getVector().getX();
|
||||
private final int thirdDirY = tertiaryDirection.getVector().getY();
|
||||
private final int thirdDirZ = tertiaryDirection.getVector().getZ();
|
||||
|
||||
protected Vector3i computeNext() {
|
||||
if (this.end) {
|
||||
return this.endOfData();
|
||||
} else {
|
||||
Vector3i cursor = new Vector3i(
|
||||
startX + this.firstDirX * this.firstIndex + this.secondDirX * this.secondIndex + this.thirdDirX * this.thirdIndex,
|
||||
startY + this.firstDirY * this.firstIndex + this.secondDirY * this.secondIndex + this.thirdDirY * this.thirdIndex,
|
||||
startZ + this.firstDirZ * this.firstIndex + this.secondDirZ * this.secondIndex + this.thirdDirZ * this.thirdIndex
|
||||
);
|
||||
|
||||
if (this.thirdIndex < tertiaryCount) {
|
||||
this.thirdIndex++;
|
||||
} else if (this.secondIndex < secondaryCount) {
|
||||
this.secondIndex++;
|
||||
this.thirdIndex = 0;
|
||||
} else if (this.firstIndex < primaryCount) {
|
||||
this.firstIndex++;
|
||||
this.thirdIndex = 0;
|
||||
this.secondIndex = 0;
|
||||
} else {
|
||||
this.end = true;
|
||||
}
|
||||
|
||||
return cursor;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SimpleCollisionBox{" +
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
package ac.grim.grimac.utils.common;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
@UtilityClass
|
||||
public class GrimArguments {
|
||||
public static final boolean TRANSACTION_KICKS = !Boolean.getBoolean("grim.disable-transaction-kick");
|
||||
public static final String API_URL = System.getProperty("grim.api-url", "https://api.grim.ac/v1/server/");
|
||||
public static final String PASTE_URL = System.getProperty("grim.paste-url", "https://paste.grim.ac/");
|
||||
public static final String PLATFORM_OVERRIDE = System.getProperty("grim.platform-override", "");
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package ac.grim.grimac.utils.common.arguments;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@Getter
|
||||
public class ArgumentOptions<T> {
|
||||
|
||||
private ArgumentOptions(Class<T> clazz, String key, Supplier<T> defaultSupplier) {
|
||||
this.clazz = clazz;
|
||||
this.key = key;
|
||||
this.defaultSupplier = defaultSupplier;
|
||||
}
|
||||
|
||||
private final Class<T> clazz;
|
||||
private String key;
|
||||
private Supplier<T> defaultSupplier;
|
||||
private Predicate<T> verifier = t -> true;
|
||||
private Function<T, T> modifier = t -> t;
|
||||
private SystemArgument.Visibility visibility = SystemArgument.Visibility.VISIBLE;
|
||||
private boolean nullable = false;
|
||||
|
||||
public static <T> Builder<T> from(Class<T> clazz, String key, @NotNull Supplier<@NotNull T> defaultValue) {
|
||||
return new Builder<>(new ArgumentOptions<>(clazz, key, defaultValue));
|
||||
}
|
||||
|
||||
public static <T> Builder<T> from(Class<T> clazz, String key, T defaultValue) {
|
||||
return new Builder<>(new ArgumentOptions<>(clazz, key, () -> defaultValue)).nullable(defaultValue == null);
|
||||
}
|
||||
|
||||
public static <T> Builder<T> from(Class<T> clazz, String key) {
|
||||
return new Builder<>(new ArgumentOptions<>(clazz, key, () -> null)).nullable(true);
|
||||
}
|
||||
|
||||
public record Builder<T>(ArgumentOptions<T> options) {
|
||||
|
||||
public Builder<T> key(String key) {
|
||||
options.key = key;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> verifier(Predicate<T> predicate) {
|
||||
options.verifier = predicate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> modifier(Function<T, T> modifier) {
|
||||
options.modifier = modifier;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> defaultSupplier(Supplier<T> supplier) {
|
||||
options.defaultSupplier = supplier;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> visibility(SystemArgument.Visibility visibility) {
|
||||
options.visibility = visibility;
|
||||
return this;
|
||||
}
|
||||
|
||||
private Builder<T> nullable(boolean nullable) {
|
||||
options.nullable = nullable;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ArgumentOptions<T> build() {
|
||||
return options;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package ac.grim.grimac.utils.common.arguments;
|
||||
|
||||
import ac.grim.grimac.platform.api.Platform;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class ArgumentUtils {
|
||||
|
||||
private static final Pattern WEBSITE_URL_PATTERN = Pattern.compile("^(https?://)?(localhost:[0-9]{1,4}|(([a-zA-Z0-9_-]+\\.)?[a-zA-Z0-9_-]+\\.[a-zA-Z]{2,3}))(/[a-zA-Z0-9_/-?=]*)?$");
|
||||
|
||||
public static <T extends Number> Predicate<T> validRange(T min, T max) {
|
||||
return number -> number.doubleValue() >= min.doubleValue() && number.doubleValue() <= max.doubleValue();
|
||||
}
|
||||
|
||||
public static Predicate<String> validURL() {
|
||||
return string -> WEBSITE_URL_PATTERN.matcher(string).matches();
|
||||
}
|
||||
|
||||
public static Function<String, String> modifyURL(Supplier<String> defaultValue) {
|
||||
return string -> {
|
||||
if (!string.endsWith("/")) string += "/";
|
||||
if (WEBSITE_URL_PATTERN.matcher(string).matches()) return string;
|
||||
//LogUtil.warn("Invalid URL: " + string);
|
||||
return defaultValue.get();
|
||||
};
|
||||
}
|
||||
|
||||
public static ArgumentOptions.Builder<Long> range(String key, long defaultValue, long min, long max) {
|
||||
return ArgumentOptions.from(Long.class, key, defaultValue)
|
||||
.verifier(validRange(min, max))
|
||||
.modifier(aLong -> Math.min(Math.max(aLong, min), max));
|
||||
}
|
||||
|
||||
public static ArgumentOptions.Builder<Integer> range(String key, int defaultValue, int min, int max) {
|
||||
return ArgumentOptions.from(Integer.class, key, defaultValue)
|
||||
.verifier(validRange(min, max))
|
||||
.modifier(integer -> Math.min(Math.max(integer, min), max));
|
||||
}
|
||||
|
||||
public static ArgumentOptions.Builder<String> url(String key, String defaultUrl) {
|
||||
return ArgumentOptions.from(String.class, key, defaultUrl)
|
||||
.verifier(ArgumentUtils.validURL())
|
||||
.modifier(modifyURL(() -> defaultUrl));
|
||||
}
|
||||
|
||||
public static ArgumentOptions.Builder<String> string(String key, String defaultValue) {
|
||||
return ArgumentOptions.from(String.class, key, defaultValue);
|
||||
}
|
||||
|
||||
public static ArgumentOptions.Builder<Boolean> string(String key, boolean defaultValue) {
|
||||
return ArgumentOptions.from(Boolean.class, key, defaultValue);
|
||||
}
|
||||
|
||||
public static ArgumentOptions.Builder<Platform> platform(String key) {
|
||||
return ArgumentOptions.from(Platform.class, key);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package ac.grim.grimac.utils.common.arguments;
|
||||
|
||||
import ac.grim.grimac.platform.api.Platform;
|
||||
|
||||
import static ac.grim.grimac.utils.common.arguments.ArgumentUtils.platform;
|
||||
import static ac.grim.grimac.utils.common.arguments.ArgumentUtils.string;
|
||||
|
||||
public class CommonGrimArguments {
|
||||
|
||||
private final static SystemArgumentFactory FACTORY = SystemArgumentFactory.Builder.of("Grim")
|
||||
.optionModifier(builder -> builder.key("Grim" + builder.options().getKey()))
|
||||
.supportEnv()
|
||||
.build();
|
||||
|
||||
public final static SystemArgument<Boolean> KICK_ON_TRANSACTION_ERRORS = FACTORY.create(string("KickOnTransactionTaskErrors", false));
|
||||
public final static SystemArgument<String> API_URL = FACTORY.create(string("APIUrl", "https://api.grim.ac/v1/server/"));
|
||||
public final static SystemArgument<String> PASTE_URL = FACTORY.create(string("PasteUrl", "https://paste.grim.ac/"));
|
||||
public final static SystemArgument<Platform> PLATFORM_OVERRIDE = FACTORY.create(platform("PlatformOverride"));
|
||||
|
||||
/**
|
||||
* Enables "Fast Bypass" mode for chat messages sent by GrimAC.
|
||||
* <p>
|
||||
* <b>BENEFIT:</b> Messages are sent directly as packets, significantly improving
|
||||
* performance and reducing server overhead especially when lots of alerts are being sent.
|
||||
* <p>
|
||||
* <b>TRADE-OFF:</b> This completely bypasses the platform's event system (e.g., Bukkit's chat events).
|
||||
* Other plugins will NOT be able to see, format, or cancel these messages.
|
||||
* <p>
|
||||
* This setting is opt-in (default: false) and requires a server restart to change.
|
||||
*/
|
||||
public final static SystemArgument<Boolean> USE_CHAT_FAST_BYPASS = FACTORY.create(string("ChatFastBypass", true));
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package ac.grim.grimac.utils.common.arguments;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public record SystemArgument<T>(String key, Class<T> clazz, T value, boolean set,
|
||||
Visibility visibility) {
|
||||
|
||||
public boolean matches(Predicate<T> predicate) {
|
||||
return predicate.test(value);
|
||||
}
|
||||
|
||||
public <K> K mapValue(Function<T, K> mapper, K otherwise) {
|
||||
try {
|
||||
return value == null ? otherwise : mapper.apply(value);
|
||||
} catch (Exception e) {
|
||||
//TODO: add back logging once LogUtil has been refactored
|
||||
//LogUtil.exception("Failed to map value for argument " + key, e);
|
||||
}
|
||||
return otherwise;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
SystemArgument<?> that = (SystemArgument<?>) o;
|
||||
return key.equals(that.key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return key.hashCode();
|
||||
}
|
||||
|
||||
public enum Visibility {
|
||||
VISIBLE, // visible from commands
|
||||
HIDDEN, // only visible in console
|
||||
SECRET // only visible in console if set
|
||||
}
|
||||
|
||||
}
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
package ac.grim.grimac.utils.common.arguments;
|
||||
|
||||
import ac.grim.grimac.platform.api.Platform;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
public record SystemArgumentFactory(Map<String, String> arguments,
|
||||
Map<Class<?>, Function<String, ?>> parsers,
|
||||
Consumer<SystemArgument<?>> creationListener,
|
||||
Consumer<ArgumentOptions.Builder<?>> optionModifier) {
|
||||
|
||||
public static class Builder {
|
||||
|
||||
public static Builder of(final String prefix) {
|
||||
return new Builder(prefix);
|
||||
}
|
||||
|
||||
public Builder onRegister(Consumer<SystemArgument<?>> listener) {
|
||||
this.registerListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder optionModifier(Consumer<ArgumentOptions.Builder<?>> modifier) {
|
||||
this.optionModifier = modifier;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder supportEnv() {
|
||||
this.envSupport = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
private final String prefix;
|
||||
private boolean envSupport = false;
|
||||
private Consumer<SystemArgument<?>> registerListener = argument -> {
|
||||
};
|
||||
private final Map<Class<?>, Function<String, ?>> parseBuilder;
|
||||
private Consumer<ArgumentOptions.Builder<?>> optionModifier = null;
|
||||
|
||||
private Builder(final String prefix) {
|
||||
this.prefix = prefix;
|
||||
this.parseBuilder = new HashMap<>();
|
||||
// Register default parsers
|
||||
registerDefaultParsers();
|
||||
}
|
||||
|
||||
protected void registerDefaultParsers() {
|
||||
registerParser(Boolean.class, Boolean::parseBoolean)
|
||||
.registerParser(Byte.class, Byte::parseByte)
|
||||
.registerParser(Short.class, Short::parseShort)
|
||||
.registerParser(Integer.class, Integer::parseInt)
|
||||
.registerParser(Float.class, Float::parseFloat)
|
||||
.registerParser(Double.class, Double::parseDouble)
|
||||
.registerParser(Long.class, Long::parseLong)
|
||||
.registerParser(Character.class, s -> !s.isEmpty() ? s.charAt(0) : '\0')
|
||||
.registerParser(char[].class, String::toCharArray)
|
||||
.registerParser(String.class, String::valueOf)
|
||||
.registerParser(Charset.class, Charset::forName)
|
||||
.registerParser(Platform.class, Platform::getByName);
|
||||
}
|
||||
|
||||
public <T> Builder registerParser(Class<T> type, Function<String, T> parser) {
|
||||
parseBuilder.put(type, parser);
|
||||
return this;
|
||||
}
|
||||
|
||||
private void updateFromEnv(Map<String, String> builder) {
|
||||
for (Map.Entry<String, String> entry : System.getenv().entrySet()) {
|
||||
if (entry.getKey().startsWith(prefix.toUpperCase())) {
|
||||
if (builder.put(entry.getKey().toLowerCase(), entry.getValue()) != null) {
|
||||
warn("Env variable overwriting system variable: " + entry.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public SystemArgumentFactory build() {
|
||||
// Parse arguments from JVM arguments
|
||||
final String findPrefix = "-d" + prefix.toLowerCase();
|
||||
Map<String, String> builder = getSystemPropertiesMap(findPrefix);
|
||||
if (envSupport) {
|
||||
try {
|
||||
updateFromEnv(builder);
|
||||
} catch (Exception e) {
|
||||
exception("Failed to read environment variables", e);
|
||||
}
|
||||
}
|
||||
//
|
||||
return new SystemArgumentFactory(Map.copyOf(builder), Map.copyOf(parseBuilder), registerListener, optionModifier);
|
||||
}
|
||||
|
||||
protected @NotNull Map<String, String> getSystemPropertiesMap(String findPrefix) {
|
||||
Map<String, String> builder = new HashMap<>();
|
||||
for (String line : ManagementFactory.getRuntimeMXBean().getInputArguments()) {
|
||||
if (line.toLowerCase().startsWith(findPrefix)) {
|
||||
int index = line.indexOf('=');
|
||||
if (index > 0 && index < line.length() - 1) {
|
||||
String key = line.substring(2, index); // remove -D
|
||||
String value = line.substring(index + 1);
|
||||
builder.put(key.toLowerCase(), value);
|
||||
} else {
|
||||
warn("Invalid startup argument: " + line);
|
||||
}
|
||||
}
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
||||
private <T> SystemArgument<T> createDefaultSupplier(ArgumentOptions<T> options) {
|
||||
T value = options.getModifier().apply(options.getDefaultSupplier().get());
|
||||
if (value == null && !options.isNullable())
|
||||
throw new IllegalArgumentException("Default value cannot be null for startup argument \"" + options.getKey() + "\"");
|
||||
if ((value != null && !options.getVerifier().test(value)))
|
||||
throw new IllegalArgumentException("Invalid default value for startup argument \"" + options.getKey() + "\"");
|
||||
SystemArgument<T> argument = new SystemArgument<>(options.getKey(), options.getClazz(), value, false, options.getVisibility());
|
||||
creationListener.accept(argument);
|
||||
return argument;
|
||||
}
|
||||
|
||||
public <T> SystemArgument<T> create(ArgumentOptions.Builder<T> builder) {
|
||||
if (optionModifier != null) optionModifier.accept(builder);
|
||||
final ArgumentOptions<T> options = builder.build();
|
||||
//
|
||||
final String value = arguments.get(options.getKey().toLowerCase());
|
||||
if (value == null) return createDefaultSupplier(options);
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
Function<String, T> parser = (Function<String, T>) parsers.get(options.getClazz());
|
||||
if (parser == null) return createDefaultSupplier(options);
|
||||
T parsed = options.getModifier().apply(parser.apply(value));
|
||||
if (parsed == null || !options.getVerifier().test(parsed))
|
||||
return createDefaultSupplier(options);
|
||||
SystemArgument<T> newArgument = new SystemArgument<>(options.getKey(), options.getClazz(), parsed, true, options.getVisibility());
|
||||
creationListener.accept(newArgument);
|
||||
return newArgument;
|
||||
} catch (Exception e) {
|
||||
exception("Failed to parse value for startup argument \"" + options.getKey() + "\"", e);
|
||||
return createDefaultSupplier(options);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, String> getFoundArguments() {
|
||||
return arguments;
|
||||
}
|
||||
|
||||
//TODO: add back logging once LogUtil has been refactored
|
||||
|
||||
private static void exception(String message, Exception e) {
|
||||
//LogUtil.exception(message, e);
|
||||
}
|
||||
|
||||
private static void warn(String message) {
|
||||
//LogUtil.warn(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -29,6 +29,7 @@ public class PacketStateData {
|
||||
public boolean lastServerTransWasValid = false;
|
||||
@Getter
|
||||
private int slowedByUsingItemSlot = Integer.MIN_VALUE;
|
||||
public boolean sendingBundlePacket;
|
||||
|
||||
// If true, the player's rotation was forced to the horse's rotation only on 1.13-
|
||||
public boolean horseInteractCausedForcedRotation = false;
|
||||
|
||||
@@ -152,8 +152,9 @@ public class PacketEntity extends TypedPacketEntity {
|
||||
// TODO make config option to rewrite Rots to PosRots instead of expanding to handle this false
|
||||
// https://bugs.mojang.com/browse/MC-255263
|
||||
if (!hasPos &&
|
||||
(player.getClientVersion().isNewerThan(ClientVersion.V_1_21_4) ||
|
||||
(player.getClientVersion().isOlderThan(ClientVersion.V_1_20_2)) && player.getClientVersion().isNewerThan(ClientVersion.V_1_14_4))
|
||||
// Fixed in 1.21.9 again
|
||||
(player.getClientVersion().isOlderThan(ClientVersion.V_1_21_9) && player.getClientVersion().isNewerThan(ClientVersion.V_1_21_4)) ||
|
||||
(player.getClientVersion().isOlderThan(ClientVersion.V_1_20_2) && player.getClientVersion().isNewerThan(ClientVersion.V_1_14_4))
|
||||
) {
|
||||
newPacketLocation.cancelLerp();
|
||||
}
|
||||
|
||||
@@ -536,7 +536,7 @@ public class CompensatedWorld {
|
||||
WrappedBlockState block = getBlock(x, y, z);
|
||||
|
||||
if (block.getType() == StateTypes.DETECTOR_RAIL) { // Rails hard power block below itself
|
||||
boolean isPowered = (boolean) block.getInternalData().getOrDefault(StateValue.POWERED, false);
|
||||
boolean isPowered = block.hasProperty(StateValue.POWERED) && block.isPowered();
|
||||
return face == BlockFace.UP && isPowered ? 15 : 0;
|
||||
} else if (block.getType() == StateTypes.REDSTONE_TORCH) {
|
||||
return face != BlockFace.UP && block.isLit() ? 15 : 0;
|
||||
|
||||
@@ -4,7 +4,7 @@ import ac.grim.grimac.GrimAPI;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.anticheat.LogUtil;
|
||||
import ac.grim.grimac.utils.anticheat.MessageUtil;
|
||||
import ac.grim.grimac.utils.common.GrimArguments;
|
||||
import ac.grim.grimac.utils.common.arguments.CommonGrimArguments;
|
||||
import ac.grim.grimac.utils.data.Pair;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -91,7 +91,7 @@ public class LatencyUtils {
|
||||
} catch (Exception e) {
|
||||
LogUtil.error("An error has occurred when running transactions for player: " + player.user.getName(), e);
|
||||
// Kick the player SO PEOPLE ACTUALLY REPORT PROBLEMS AND KNOW WHEN THEY HAPPEN
|
||||
if (GrimArguments.TRANSACTION_KICKS) {
|
||||
if (CommonGrimArguments.KICK_ON_TRANSACTION_ERRORS.value()) {
|
||||
player.disconnect(MessageUtil.miniMessage(MessageUtil.replacePlaceholders(player, GrimAPI.INSTANCE.getConfigManager().getDisconnectPacketError())));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package ac.grim.grimac.utils.math;
|
||||
|
||||
import com.github.retrooper.packetevents.util.Vector3i;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -186,6 +187,11 @@ public class GrimMath {
|
||||
private static final int Z_OFFSET = PACKED_Y_LENGTH;
|
||||
private static final int X_OFFSET = PACKED_Y_LENGTH + PACKED_HORIZONTAL_LENGTH;
|
||||
|
||||
@Contract(pure = true)
|
||||
public static long asLong(Vector3i vector) {
|
||||
return asLong(vector.getX(), vector.getY(), vector.getZ());
|
||||
}
|
||||
|
||||
@Contract(pure = true)
|
||||
public static long asLong(int x, int y, int z) {
|
||||
return (x & PACKED_X_MASK) << X_OFFSET
|
||||
|
||||
@@ -21,9 +21,8 @@ import lombok.experimental.UtilityClass;
|
||||
public final class BoundingBoxSize {
|
||||
|
||||
public static float getWidth(GrimPlayer player, PacketEntity packetEntity) {
|
||||
// Turtles are the only baby animal that don't follow the * 0.5 rule
|
||||
if (packetEntity.type == EntityTypes.TURTLE && packetEntity.isBaby) return 0.36f;
|
||||
return getWidthMinusBaby(player, packetEntity) * (packetEntity.isBaby ? 0.5f : 1f);
|
||||
float width = getWidthMinusBaby(player, packetEntity);
|
||||
return width * (packetEntity.isBaby ? getBabyScaleFactor(packetEntity) : 1f);
|
||||
}
|
||||
|
||||
private static float getWidthMinusBaby(GrimPlayer player, PacketEntity packetEntity) {
|
||||
@@ -186,9 +185,8 @@ public final class BoundingBoxSize {
|
||||
}
|
||||
|
||||
public static float getHeight(GrimPlayer player, PacketEntity packetEntity) {
|
||||
// Turtles are the only baby animal that don't follow the * 0.5 rule
|
||||
if (packetEntity.type == EntityTypes.TURTLE && packetEntity.isBaby) return 0.12f;
|
||||
return getHeightMinusBaby(player, packetEntity) * (packetEntity.isBaby ? 0.5f : 1f);
|
||||
float height = getHeightMinusBaby(player, packetEntity);
|
||||
return height * (packetEntity.isBaby ? getBabyScaleFactor(packetEntity) : 1f);
|
||||
}
|
||||
|
||||
public static double getMyRidingOffset(PacketEntity packetEntity) {
|
||||
@@ -410,4 +408,14 @@ public final class BoundingBoxSize {
|
||||
}
|
||||
return 1.95f;
|
||||
}
|
||||
|
||||
private static float getBabyScaleFactor(PacketEntity packetEntity) {
|
||||
final EntityType type = packetEntity.type;
|
||||
if (type == EntityTypes.TURTLE) return 0.3f;
|
||||
if (type == EntityTypes.HAPPY_GHAST) return 0.2375f;
|
||||
if (type == EntityTypes.DOLPHIN) return 0.65f;
|
||||
if (type == EntityTypes.ARMADILLO) return 0.6f;
|
||||
if (type == EntityTypes.CAMEL) return 0.45f;
|
||||
return 0.5f;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,12 @@ package ac.grim.grimac.utils.nmsutil;
|
||||
|
||||
import ac.grim.grimac.events.packets.PacketWorldBorder;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.predictionengine.blockeffects.BlockEffectsResolver;
|
||||
import ac.grim.grimac.predictionengine.blockeffects.impl.BlockEffectsResolverV1_21_10;
|
||||
import ac.grim.grimac.predictionengine.blockeffects.impl.BlockEffectsResolverV1_21_2;
|
||||
import ac.grim.grimac.predictionengine.blockeffects.impl.BlockEffectsResolverV1_21_4;
|
||||
import ac.grim.grimac.predictionengine.blockeffects.impl.BlockEffectsResolverV1_21_5;
|
||||
import ac.grim.grimac.predictionengine.blockeffects.impl.BlockEffectsResolverV1_21_6;
|
||||
import ac.grim.grimac.utils.chunks.Column;
|
||||
import ac.grim.grimac.utils.collisions.CollisionData;
|
||||
import ac.grim.grimac.utils.collisions.datatypes.CollisionBox;
|
||||
@@ -26,27 +32,21 @@ import com.github.retrooper.packetevents.protocol.world.states.type.StateType;
|
||||
import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes;
|
||||
import com.github.retrooper.packetevents.util.Vector3d;
|
||||
import com.github.retrooper.packetevents.util.Vector3i;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import it.unimi.dsi.fastutil.floats.FloatArraySet;
|
||||
import it.unimi.dsi.fastutil.floats.FloatArrays;
|
||||
import it.unimi.dsi.fastutil.floats.FloatSet;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.longs.LongSet;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@UtilityClass
|
||||
public final class Collisions {
|
||||
private static final double COLLISION_EPSILON = 1.0E-7;
|
||||
public static final double COLLISION_EPSILON = 1.0E-7;
|
||||
|
||||
private static final boolean IS_FOURTEEN = PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_14); // Optimization for chunks with empty block count
|
||||
private static final List<List<Axis>> allAxisCombinations = Arrays.asList(
|
||||
@@ -448,9 +448,9 @@ public final class Collisions {
|
||||
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_21_2)) return;
|
||||
// Use the bounding box for after the player's movement is applied
|
||||
double expandAmount = player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_19_4) ? 1e-5 : 0.001;
|
||||
SimpleCollisionBox aABB = player.inVehicle()
|
||||
? GetBoundingBox.getCollisionBoxForPlayer(player, player.x, player.y, player.z).expand(-expandAmount)
|
||||
: player.boundingBox.copy().expand(-expandAmount);
|
||||
SimpleCollisionBox aABB = (player.inVehicle()
|
||||
? GetBoundingBox.getCollisionBoxForPlayer(player, player.x, player.y, player.z)
|
||||
: player.boundingBox.copy()).expand(-expandAmount);
|
||||
|
||||
Location blockPos = new Location(null, aABB.minX, aABB.minY, aABB.minZ);
|
||||
Location blockPos2 = new Location(null, aABB.maxX, aABB.maxY, aABB.maxZ);
|
||||
@@ -468,13 +468,13 @@ public final class Collisions {
|
||||
continue;
|
||||
}
|
||||
|
||||
onInsideBlock(player, blockType, block, blockX, blockY, blockZ);
|
||||
onInsideBlock(player, blockType, block, blockX, blockY, blockZ, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void onInsideBlock(GrimPlayer player, StateType blockType, WrappedBlockState block, int blockX, int blockY, int blockZ) {
|
||||
public static void onInsideBlock(GrimPlayer player, StateType blockType, WrappedBlockState block, int blockX, int blockY, int blockZ, boolean magic) {
|
||||
if (blockType == StateTypes.COBWEB) {
|
||||
if (player.compensatedEntities.hasPotionEffect(PotionTypes.WEAVING)) {
|
||||
player.stuckSpeedMultiplier = new Vector3dm(0.5, 0.25, 0.5);
|
||||
@@ -502,7 +502,7 @@ public final class Collisions {
|
||||
player.wasTouchingLava = true;
|
||||
}
|
||||
|
||||
if (blockType == StateTypes.BUBBLE_COLUMN && player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_13)) {
|
||||
if (blockType == StateTypes.BUBBLE_COLUMN && player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_13) && magic) {
|
||||
WrappedBlockState blockAbove = player.compensatedWorld.getBlock(blockX, blockY + 1, blockZ);
|
||||
|
||||
if (player.inVehicle() && player.compensatedEntities.self.getRiding().isBoat) {
|
||||
@@ -556,218 +556,65 @@ public final class Collisions {
|
||||
|
||||
// Implementation of Collisions#handleInsideBlocks for >= 1.21.2
|
||||
public static void applyEffectsFromBlocks(GrimPlayer player) {
|
||||
for (GrimPlayer.Movement movement : player.finalMovementsThisTick) {
|
||||
Vector3d from = movement.from();
|
||||
Vector3d to = movement.to().subtract(movement.from());
|
||||
if (movement.axisIndependant() && to.lengthSquared() > 0.0) {
|
||||
for (Axis axis : Collisions.axisStepOrder(to)) {
|
||||
double value = axis.choose(to.getX(), to.getY(), to.getZ());
|
||||
if (value != 0.0) {
|
||||
Vector3d vector = Collisions.relative(from, axis.getPositive(), value);
|
||||
Collisions.checkInsideBlocks(player, from, vector);
|
||||
from = vector;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Collisions.checkInsideBlocks(player, movement.from(), movement.to());
|
||||
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_21_2)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset stuck speed so it can update
|
||||
if (player.stuckSpeedMultiplier.getX() < 0.99) {
|
||||
player.uncertaintyHandler.lastStuckSpeedMultiplier.reset();
|
||||
}
|
||||
|
||||
player.stuckSpeedMultiplier = new Vector3dm(1, 1, 1);
|
||||
player.finalMovementsThisTick.clear();
|
||||
|
||||
Vector3d from = new Vector3d(player.lastX, player.lastY, player.lastZ);
|
||||
Vector3d to = new Vector3d(player.x, player.y, player.z);
|
||||
|
||||
ClientVersion clientVersion = player.getClientVersion();
|
||||
if (clientVersion.isOlderThan(ClientVersion.V_1_21_5)) {
|
||||
player.finalMovementsThisTick.add(new GrimPlayer.Movement(from, to));
|
||||
} else if (clientVersion.isNewerThanOrEquals(ClientVersion.V_1_21_5)) {
|
||||
player.finalMovementsThisTick.addAll(player.movementThisTick);
|
||||
player.movementThisTick.clear();
|
||||
|
||||
if (player.finalMovementsThisTick.isEmpty()) {
|
||||
player.finalMovementsThisTick.add(new GrimPlayer.Movement(from, to));
|
||||
} else if (player.finalMovementsThisTick.get(player.finalMovementsThisTick.size() - 1).to().distanceSquared(to) > 9.9999994E-11F) {
|
||||
player.finalMovementsThisTick.add(new GrimPlayer.Movement(player.finalMovementsThisTick.get(player.finalMovementsThisTick.size() - 1).to(), to));
|
||||
}
|
||||
}
|
||||
|
||||
player.visitedBlocks.clear();
|
||||
}
|
||||
Collisions.resolveBlockEffects(player);
|
||||
|
||||
public static void checkInsideBlocks(GrimPlayer player, Vector3d from, Vector3d to) {
|
||||
SimpleCollisionBox boundingBox = (player.getClientVersion() == ClientVersion.V_1_21_2 ?
|
||||
player.boundingBox.copy() : GetBoundingBox.getCollisionBoxForPlayer(player, to.x, to.y, to.z)).expand(-1.0E-5F);
|
||||
if (player.stuckSpeedMultiplier.getX() < 0.9) {
|
||||
// Reset fall distance if stuck in block
|
||||
player.fallDistance = 0;
|
||||
}
|
||||
|
||||
for (Vector3i blockPos : Collisions.boxTraverseBlocks(player, from, to, boundingBox)) {
|
||||
WrappedBlockState blockState = player.compensatedWorld.getBlock(blockPos);
|
||||
StateType blockType = blockState.getType();
|
||||
|
||||
if (blockType.isAir()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (player.visitedBlocks.add(GrimMath.asLong(blockPos.getX(), blockPos.getY(), blockPos.getZ()))) {
|
||||
Collisions.onInsideBlock(player, blockType, blockState, blockPos.x, blockPos.y, blockPos.z);
|
||||
}
|
||||
// Flying players are not affected by cobwebs/sweet berry bushes
|
||||
if (player.isFlying) {
|
||||
player.stuckSpeedMultiplier = new Vector3dm(1, 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public static Iterable<Vector3i> boxTraverseBlocks(GrimPlayer player, Vector3d start, Vector3d end, SimpleCollisionBox boundingBox) {
|
||||
Vector3d direction = end.subtract(start);
|
||||
Iterable<Vector3i> initialBlocks = SimpleCollisionBox.betweenClosed(boundingBox);
|
||||
if (direction.lengthSquared() < (double) GrimMath.square(0.99999F)) {
|
||||
return initialBlocks;
|
||||
public static void resolveBlockEffects(GrimPlayer player) {
|
||||
ClientVersion version = player.getClientVersion();
|
||||
BlockEffectsResolver resolver;
|
||||
|
||||
if (version == ClientVersion.V_1_21_2) {
|
||||
resolver = BlockEffectsResolverV1_21_2.INSTANCE; // 1.21.2-1.21.3
|
||||
} else if (version == ClientVersion.V_1_21_4) {
|
||||
resolver = BlockEffectsResolverV1_21_4.INSTANCE; // 1.21.4
|
||||
} else if (version == ClientVersion.V_1_21_5) {
|
||||
resolver = BlockEffectsResolverV1_21_5.INSTANCE; // 1.21.5
|
||||
} else if (version.isNewerThanOrEquals(ClientVersion.V_1_21_6) && version.isOlderThanOrEquals(ClientVersion.V_1_21_7)) {
|
||||
resolver = BlockEffectsResolverV1_21_6.INSTANCE; // 1.21.6-1.21.8
|
||||
} else {
|
||||
LongSet alreadyVisited = player.getClientVersion().isOlderThan(ClientVersion.V_1_21_5) ? null : new LongOpenHashSet();
|
||||
Set<Vector3i> traversedBlocks = new ObjectLinkedOpenHashSet<>();
|
||||
Vector3d boxMinPosition = boundingBox.min().toVector3d();
|
||||
Vector3d subtractedMinPosition = boxMinPosition.subtract(direction);
|
||||
addCollisionsAlongTravel(alreadyVisited, traversedBlocks, subtractedMinPosition, boxMinPosition, boundingBox);
|
||||
|
||||
for (Vector3i blockPos : initialBlocks) {
|
||||
if (alreadyVisited == null || !alreadyVisited.contains(GrimMath.asLong(blockPos.getX(), blockPos.getY(), blockPos.getZ()))) {
|
||||
traversedBlocks.add(blockPos);
|
||||
}
|
||||
}
|
||||
|
||||
return traversedBlocks;
|
||||
}
|
||||
}
|
||||
|
||||
public static void addCollisionsAlongTravel(LongSet alreadyVisited, Set<Vector3i> output, Vector3d start, Vector3d end, SimpleCollisionBox boundingBox) {
|
||||
Vector3d direction = end.subtract(start);
|
||||
int currentX = GrimMath.floor(start.x);
|
||||
int currentY = GrimMath.floor(start.y);
|
||||
int currentZ = GrimMath.floor(start.z);
|
||||
int stepX = GrimMath.sign(direction.x);
|
||||
int stepY = GrimMath.sign(direction.y);
|
||||
int stepZ = GrimMath.sign(direction.z);
|
||||
double tMaxX = stepX == 0 ? Double.MAX_VALUE : stepX / direction.x;
|
||||
double tMaxY = stepY == 0 ? Double.MAX_VALUE : stepY / direction.y;
|
||||
double tMaxZ = stepZ == 0 ? Double.MAX_VALUE : stepZ / direction.z;
|
||||
double tDeltaX = tMaxX * (stepX > 0 ? 1.0 - GrimMath.frac(start.x) : GrimMath.frac(start.x));
|
||||
double tDeltaY = tMaxY * (stepY > 0 ? 1.0 - GrimMath.frac(start.y) : GrimMath.frac(start.y));
|
||||
double tDeltaZ = tMaxZ * (stepZ > 0 ? 1.0 - GrimMath.frac(start.z) : GrimMath.frac(start.z));
|
||||
int iterationCount = 0;
|
||||
|
||||
while (tDeltaX <= 1.0 || tDeltaY <= 1.0 || tDeltaZ <= 1.0) {
|
||||
if (tDeltaX < tDeltaY) {
|
||||
if (tDeltaX < tDeltaZ) {
|
||||
currentX += stepX;
|
||||
tDeltaX += tMaxX;
|
||||
} else {
|
||||
currentZ += stepZ;
|
||||
tDeltaZ += tMaxZ;
|
||||
}
|
||||
} else if (tDeltaY < tDeltaZ) {
|
||||
currentY += stepY;
|
||||
tDeltaY += tMaxY;
|
||||
} else {
|
||||
currentZ += stepZ;
|
||||
tDeltaZ += tMaxZ;
|
||||
}
|
||||
|
||||
if (iterationCount++ > 16) {
|
||||
break;
|
||||
}
|
||||
|
||||
Optional<Vector3d> collisionPoint = clip(currentX, currentY, currentZ, currentX + 1, currentY + 1, currentZ + 1, start, end);
|
||||
if (collisionPoint.isPresent()) {
|
||||
Vector3d collisionVec = collisionPoint.get();
|
||||
double clampedX = GrimMath.clamp(collisionVec.x, currentX + 1.0E-5F, currentX + 1.0 - 1.0E-5F);
|
||||
double clampedY = GrimMath.clamp(collisionVec.y, currentY + 1.0E-5F, currentY + 1.0 - 1.0E-5F);
|
||||
double clampedZ = GrimMath.clamp(collisionVec.z, currentZ + 1.0E-5F, currentZ + 1.0 - 1.0E-5F);
|
||||
int endX = GrimMath.floor(clampedX + boundingBox.getXSize());
|
||||
int endY = GrimMath.floor(clampedY + boundingBox.getYSize());
|
||||
int endZ = GrimMath.floor(clampedZ + boundingBox.getZSize());
|
||||
|
||||
for (int x = currentX; x <= endX; x++) {
|
||||
for (int y = currentY; y <= endY; y++) {
|
||||
for (int z = currentZ; z <= endZ; z++) {
|
||||
if (alreadyVisited == null || alreadyVisited.add(GrimMath.asLong(x, y, z))) {
|
||||
output.add(new Vector3i(x, y, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Optional<Vector3d> clip(double minX, double minY, double minZ, double maxX, double maxY, double maxZ, Vector3d start, Vector3d end) {
|
||||
double[] minDistance = new double[]{1.0};
|
||||
double deltaX = end.x - start.x;
|
||||
double deltaY = end.y - start.y;
|
||||
double deltaZ = end.z - start.z;
|
||||
Direction direction = getDirection(minX, minY, minZ, maxX, maxY, maxZ, start, minDistance, null, deltaX, deltaY, deltaZ);
|
||||
if (direction == null) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
double distance = minDistance[0];
|
||||
return Optional.of(start.add(distance * deltaX, distance * deltaY, distance * deltaZ));
|
||||
}
|
||||
}
|
||||
|
||||
private static Direction getDirection(
|
||||
double minX,
|
||||
double minY,
|
||||
double minZ,
|
||||
double maxX,
|
||||
double maxY,
|
||||
double maxZ,
|
||||
Vector3d start,
|
||||
double[] minDistance,
|
||||
Direction facing,
|
||||
double deltaX,
|
||||
double deltaY,
|
||||
double deltaZ
|
||||
) {
|
||||
if (deltaX > COLLISION_EPSILON) {
|
||||
facing = clipPoint(minDistance, facing, deltaX, deltaY, deltaZ, minX, minY, maxY, minZ, maxZ, Direction.WEST, start.x, start.y, start.z);
|
||||
} else if (deltaX < -COLLISION_EPSILON) {
|
||||
facing = clipPoint(minDistance, facing, deltaX, deltaY, deltaZ, maxX, minY, maxY, minZ, maxZ, Direction.EAST, start.x, start.y, start.z);
|
||||
resolver = BlockEffectsResolverV1_21_10.INSTANCE; // 1.21.10
|
||||
}
|
||||
|
||||
if (deltaY > COLLISION_EPSILON) {
|
||||
facing = clipPoint(minDistance, facing, deltaY, deltaZ, deltaX, minY, minZ, maxZ, minX, maxX, Direction.DOWN, start.y, start.z, start.x);
|
||||
} else if (deltaY < -COLLISION_EPSILON) {
|
||||
facing = clipPoint(minDistance, facing, deltaY, deltaZ, deltaX, maxY, minZ, maxZ, minX, maxX, Direction.UP, start.y, start.z, start.x);
|
||||
}
|
||||
|
||||
if (deltaZ > COLLISION_EPSILON) {
|
||||
facing = clipPoint(minDistance, facing, deltaZ, deltaX, deltaY, minZ, minX, maxX, minY, maxY, Direction.NORTH, start.z, start.x, start.y);
|
||||
} else if (deltaZ < -COLLISION_EPSILON) {
|
||||
facing = clipPoint(minDistance, facing, deltaZ, deltaX, deltaY, maxZ, minX, maxX, minY, maxY, Direction.SOUTH, start.z, start.x, start.y);
|
||||
}
|
||||
|
||||
return facing;
|
||||
}
|
||||
|
||||
public static Direction clipPoint(
|
||||
double[] minDistance,
|
||||
Direction prevDirection,
|
||||
double distanceSide,
|
||||
double distanceOtherA,
|
||||
double distanceOtherB,
|
||||
double minSide,
|
||||
double minOtherA,
|
||||
double maxOtherA,
|
||||
double minOtherB,
|
||||
double maxOtherB,
|
||||
Direction hitSide,
|
||||
double startSide,
|
||||
double startOtherA,
|
||||
double startOtherB
|
||||
) {
|
||||
double sideDistance = (minSide - startSide) / distanceSide;
|
||||
double otherDistanceA = startOtherA + sideDistance * distanceOtherA;
|
||||
double otherDistanceB = startOtherB + sideDistance * distanceOtherB;
|
||||
if (sideDistance > 0.0 && sideDistance < minDistance[0] &&
|
||||
minOtherA - COLLISION_EPSILON < otherDistanceA &&
|
||||
otherDistanceA < maxOtherA + COLLISION_EPSILON &&
|
||||
minOtherB - COLLISION_EPSILON < otherDistanceB &&
|
||||
otherDistanceB < maxOtherB + COLLISION_EPSILON) {
|
||||
minDistance[0] = sideDistance;
|
||||
return hitSide;
|
||||
} else {
|
||||
return prevDirection;
|
||||
}
|
||||
}
|
||||
|
||||
public static final ImmutableList<Axis> YXZ_AXIS_ORDER = ImmutableList.of(Collisions.Axis.Y, Collisions.Axis.X, Collisions.Axis.Z);
|
||||
public static final ImmutableList<Collisions.Axis> YZX_AXIS_ORDER = ImmutableList.of(Collisions.Axis.Y, Collisions.Axis.Z, Collisions.Axis.X);
|
||||
|
||||
public static Iterable<Collisions.Axis> axisStepOrder(Vector3d vector) {
|
||||
return Math.abs(vector.getX()) < Math.abs(vector.getZ()) ? YZX_AXIS_ORDER : YXZ_AXIS_ORDER;
|
||||
}
|
||||
|
||||
public static Vector3d relative(Vector3d curr, Direction direction, double value) {
|
||||
Vector3i vec = direction.getVector();
|
||||
return new Vector3d(
|
||||
curr.x + value * vec.getX(), curr.y + value * vec.getY(), curr.z + value * vec.getZ()
|
||||
);
|
||||
resolver.applyEffectsFromBlocks(player);
|
||||
}
|
||||
|
||||
private static double getOldDeltaY(GrimPlayer player, double value) {
|
||||
@@ -1078,43 +925,110 @@ public final class Collisions {
|
||||
|
||||
public enum Axis {
|
||||
X {
|
||||
@Override
|
||||
public double get(Vector3d vector) {
|
||||
return vector.getX();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int get(Vector3i vector) {
|
||||
return vector.getX();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double choose(double x, double y, double z) {
|
||||
return x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int choose(int x, int y, int z) {
|
||||
return x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Direction getPositive() {
|
||||
return Direction.EAST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Direction getNegative() {
|
||||
return Direction.WEST;
|
||||
}
|
||||
},
|
||||
Y {
|
||||
@Override
|
||||
public double get(Vector3d vector) {
|
||||
return vector.getY();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int get(Vector3i vector) {
|
||||
return vector.getY();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double choose(double x, double y, double z) {
|
||||
return y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int choose(int x, int y, int z) {
|
||||
return y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Direction getPositive() {
|
||||
return Direction.UP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Direction getNegative() {
|
||||
return Direction.DOWN;
|
||||
}
|
||||
},
|
||||
Z {
|
||||
@Override
|
||||
public double get(Vector3d vector) {
|
||||
return vector.getZ();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int get(Vector3i vector) {
|
||||
return vector.getZ();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double choose(double x, double y, double z) {
|
||||
return z;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int choose(int x, int y, int z) {
|
||||
return z;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Direction getPositive() {
|
||||
return Direction.SOUTH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Direction getNegative() {
|
||||
return Direction.NORTH;
|
||||
}
|
||||
};
|
||||
|
||||
public abstract double get(Vector3d vector);
|
||||
|
||||
public abstract int get(Vector3i vector);
|
||||
|
||||
public abstract double choose(double x, double y, double z);
|
||||
|
||||
public abstract int choose(int x, int y, int z);
|
||||
|
||||
public abstract Direction getPositive();
|
||||
|
||||
public abstract Direction getNegative();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,11 @@ public class JumpPower {
|
||||
|
||||
if (player.isSprinting) {
|
||||
float radRotation = GrimMath.radians(player.yaw);
|
||||
vector.add(new Vector3dm(-player.trigHandler.sin(radRotation) * 0.2, 0.0, player.trigHandler.cos(radRotation) * 0.2));
|
||||
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_20_5)) {
|
||||
vector.add(new Vector3dm(-player.trigHandler.sin(radRotation) * 0.2, 0.0, player.trigHandler.cos(radRotation) * 0.2));
|
||||
} else {
|
||||
vector.add(new Vector3dm(-player.trigHandler.sin(radRotation) * 0.2F, 0.0, player.trigHandler.cos(radRotation) * 0.2F));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,10 @@ public class Materials {
|
||||
private static final Set<StateType> WATER_SOURCES = new HashSet<>();
|
||||
private static final Set<StateType> WATER_SOURCES_LEGACY = new HashSet<>();
|
||||
|
||||
public static final Set<StateType> CHESTS = new HashSet<>();
|
||||
public static final Set<StateType> RODS = new HashSet<>();
|
||||
public static final Set<StateType> CHAINS = new HashSet<>();
|
||||
|
||||
private static final Set<StateType> COPPER_DOORS = new HashSet<>();
|
||||
private static final Set<StateType> COPPER_TRAPDOORS = new HashSet<>();
|
||||
|
||||
@@ -116,7 +120,19 @@ public class Materials {
|
||||
CLIENT_SIDE.addAll(BlockTags.MOB_INTERACTABLE_DOORS.getStates());
|
||||
|
||||
PANES.addAll(BlockTags.GLASS_PANES.getStates());
|
||||
PANES.addAll(BlockTags.BARS.getStates());
|
||||
PANES.add(StateTypes.IRON_BARS);
|
||||
|
||||
CHESTS.addAll(BlockTags.COPPER_CHESTS.getStates());
|
||||
CHESTS.add(StateTypes.TRAPPED_CHEST);
|
||||
CHESTS.add(StateTypes.CHEST);
|
||||
|
||||
RODS.addAll(BlockTags.LIGHTNING_RODS.getStates());
|
||||
RODS.add(StateTypes.END_ROD);
|
||||
RODS.add(StateTypes.LIGHTNING_ROD);
|
||||
|
||||
CHAINS.addAll(BlockTags.CHAINS.getStates());
|
||||
CHAINS.add(StateTypes.CHAIN);
|
||||
}
|
||||
|
||||
public static boolean isStairs(StateType type) {
|
||||
@@ -171,6 +187,18 @@ public class Materials {
|
||||
return new HashSet<>(PANES);
|
||||
}
|
||||
|
||||
public static Set<StateType> getChests() {
|
||||
return new HashSet<>(CHESTS);
|
||||
}
|
||||
|
||||
public static Set<StateType> getRods() {
|
||||
return new HashSet<>(RODS);
|
||||
}
|
||||
|
||||
public static Set<StateType> getChains() {
|
||||
return new HashSet<>(CHAINS);
|
||||
}
|
||||
|
||||
public static boolean isGlassPane(StateType type) {
|
||||
return PANES.contains(type);
|
||||
}
|
||||
@@ -239,7 +267,7 @@ public class Materials {
|
||||
if (clientVersion.isOlderThan(ClientVersion.V_1_17) && BlockTags.RAILS.contains(type))
|
||||
return false;
|
||||
// Nice check to see if waterlogged :)
|
||||
return (boolean) state.getInternalData().getOrDefault(StateValue.WATERLOGGED, false);
|
||||
return state.hasProperty(StateValue.WATERLOGGED) && state.isWaterlogged();
|
||||
}
|
||||
|
||||
public static boolean isPlaceableWaterBucket(ItemType mat) {
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package ac.grim.grimac.utils.via;
|
||||
|
||||
import ac.grim.grimac.GrimAPI;
|
||||
import ac.grim.grimac.checks.impl.chat.ChatB;
|
||||
import ac.grim.grimac.player.GrimPlayer;
|
||||
import ac.grim.grimac.utils.anticheat.LogUtil;
|
||||
import com.viaversion.viaversion.api.Via;
|
||||
import com.viaversion.viaversion.api.type.Types;
|
||||
import com.viaversion.viaversion.protocols.v1_10to1_11.Protocol1_10To1_11;
|
||||
import com.viaversion.viaversion.protocols.v1_9_1to1_9_3.packet.ServerboundPackets1_9_3;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class ViaHooks {
|
||||
public static void injectVia() {
|
||||
final var protocol = Via.getManager().getProtocolManager().getProtocol(Protocol1_10To1_11.class);
|
||||
if (protocol == null) {
|
||||
LogUtil.warn("Failed to inject ViaVersion message hook for 1.11+ clients: Protocol1_10To1_11 isn't registered!");
|
||||
return;
|
||||
}
|
||||
|
||||
protocol.registerServerbound(ServerboundPackets1_9_3.CHAT, ServerboundPackets1_9_3.CHAT, wrapper -> {
|
||||
String msg = wrapper.read(Types.STRING);
|
||||
|
||||
if (msg.length() > 100) {
|
||||
UUID uuid = wrapper.user().getProtocolInfo().getUuid();
|
||||
if (uuid != null) {
|
||||
GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(uuid);
|
||||
if (player != null && player.checkManager.getPacketCheck(ChatB.class).checkChatMessage(msg)) {
|
||||
wrapper.cancel();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
msg = msg.substring(0, 100).trim();
|
||||
}
|
||||
|
||||
wrapper.write(Types.STRING, msg);
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
@@ -105,6 +105,15 @@ public class Embed implements JsonSerializable {
|
||||
return fields(newFields);
|
||||
}
|
||||
|
||||
public @NotNull Embed footer(@Nullable EmbedFooter footer) {
|
||||
if (footer == null || footer.icon() == null && footer.text().isBlank()) {
|
||||
this.footer = null;
|
||||
} else {
|
||||
this.footer = footer;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NotNull JsonObject toJson() {
|
||||
JsonObject json = new JsonObject();
|
||||
json.addProperty("description", description());
|
||||
|
||||
@@ -13,6 +13,7 @@ reloaded: "%prefix% &fDie Konfiguration wurde neu geladen."
|
||||
reload-failed: "%prefix% &cFehler beim Neuladen der Konfiguration."
|
||||
player-not-found: "%prefix% &cSpieler ist nicht auffindbar oder geschützt!"
|
||||
player-not-this-server: "%prefix% &cSpieler ist nicht auf diesem Server!"
|
||||
sender-not-found: "%prefix% &cDu kannst diesen Befehl nicht verwenden, wenn du ausgenommen bist!"
|
||||
spectate-return: "<click:run_command:/grim stopspectating><hover:show_text:\"/grim stopspectating\">\n%prefix% &fKlicke hier, um zur vorherigen Position zurückzukehren\n</hover></click>"
|
||||
cannot-spectate-return: "%prefix% &cDas kannst du nur tun, nachdem du einen Spieler beobachtet hast."
|
||||
cannot-run-on-self: "%prefix% &cDu kannst diesen Befehl nicht auf dich selbst anwenden!"
|
||||
|
||||
@@ -14,6 +14,7 @@ reloaded: "%prefix% &fConfig has been reloaded."
|
||||
reload-failed: "%prefix% &cFailed to reload config."
|
||||
player-not-found: "%prefix% &cPlayer is exempt or offline!"
|
||||
player-not-this-server: "%prefix% &cPlayer is not on this server!"
|
||||
sender-not-found: "%prefix% &cYou cannot be exempt to use this command!"
|
||||
spectate-return: "<click:run_command:/grim stopspectating><hover:show_text:\"/grim stopspectating\">\n%prefix% &fClick here to return to previous location\n</hover></click>"
|
||||
cannot-spectate-return: "%prefix% &cYou can only do this after spectating a player"
|
||||
cannot-run-on-self: "%prefix% &cYou cannot use this command on yourself!"
|
||||
|
||||
@@ -14,6 +14,7 @@ reloaded: "%prefix% &fConfig has been reloaded."
|
||||
reload-failed: "%prefix% &cFailed to reload config."
|
||||
player-not-found: "%prefix% &c¡El jugador esta excluido o fuera de linea!"
|
||||
player-not-this-server: "%prefix% &c¡El jugador no esta en este servidor!"
|
||||
sender-not-found: "%prefix% &c¡No puedes usar este comando si estás exento!"
|
||||
spectate-return: "<click:run_command:/grim stopspectating><hover:show_text:\"/grim stopspectating\">\n%prefix% &fClick aquí para volver a la localización previa\n</hover></click>"
|
||||
cannot-spectate-return: "%prefix% &cSolo puedes hacer esto despues de espectar a un jugador"
|
||||
cannot-run-on-self: "%prefix% &cNo puedes usar este comando en ti mismo!"
|
||||
|
||||
@@ -14,6 +14,7 @@ reloaded: "%prefix% &fConfig has been reloaded."
|
||||
reload-failed: "%prefix% &cFailed to reload config."
|
||||
player-not-found: "%prefix% &cPlayer est ignoré ou hors ligne !"
|
||||
player-not-this-server: "%prefix% &cPlayer n'est pas sur ce serveur !"
|
||||
sender-not-found: "%prefix% &cVous ne pouvez pas utiliser cette commande si vous êtes exempté !"
|
||||
spectate-return: "<click:run_command:/grim stopspectating><hover:show_text:\"/grim stopspectating\">\n%prefix% &fClickez ici pour retourner à votre position d'origine\n</hover></click>"
|
||||
cannot-spectate-return: "%prefix% &cVous pouvez faire cela uniquement en étant en spectateur"
|
||||
cannot-run-on-self: "%prefix% &cVous ne pouvez pas utiliser cette commande sur vous-même !"
|
||||
|
||||
@@ -14,6 +14,7 @@ reloaded: "%prefix% &fConfig has been reloaded."
|
||||
reload-failed: "%prefix% &cFailed to reload config."
|
||||
player-not-found: "%prefix% &cIl giocatore è esente o offline!"
|
||||
player-not-this-server: "%prefix% &cIl giocatore non è in questo server!"
|
||||
sender-not-found: "%prefix% &cNon puoi usare questo comando se sei esente!"
|
||||
spectate-return: "<click:run_command:/grim stopspectating><hover:show_text:\"/grim stopspectating\">\n%prefix% &fClicca qui per tornare alla posizione precedente\n</hover></click>"
|
||||
cannot-spectate-return: "%prefix% &cPuoi farlo solo dopo aver osservato un giocatore"
|
||||
cannot-run-on-self: "%prefix% &cNon puoi usare questo comando su te stesso!"
|
||||
|
||||
@@ -11,6 +11,7 @@ client-brand-format: "%prefix% &f%player% が %brand% を使用して参加し
|
||||
console-specify-target: "%prefix% &cコンソールでターゲットを指定する必要があります!"
|
||||
player-not-found: "%prefix% &cプレイヤーはチェックから免除されているか、オフラインです!"
|
||||
player-not-this-server: "%prefix% &cプレイヤーはこのサーバーにいません!"
|
||||
sender-not-found: "%prefix% &c免除状態の場合、このコマンドは使用できません!"
|
||||
spectate-return: "\n%prefix% &f以前の場所に戻るにはここをクリック\n"
|
||||
cannot-spectate-return: "%prefix% &c観戦しているプレイヤー以外ではこの操作はできません"
|
||||
cannot-run-on-self: "%prefix% &c自分に対してこのコマンドは使用できません!"
|
||||
|
||||
@@ -14,6 +14,7 @@ reloaded: "%prefix% &fConfig has been reloaded."
|
||||
reload-failed: "%prefix% &cFailed to reload config."
|
||||
player-not-found: "%prefix% &cSpeler is vrijgesteld of offline!"
|
||||
player-not-this-server: "%prefix% &cSpeler is niet op deze server!"
|
||||
sender-not-found: "%prefix% &cJe kunt dit commando niet gebruiken als je vrijgesteld bent!"
|
||||
spectate-return: "<click:run_command:/grim stopspectating><hover:show_text:\"/grim stopspectating\">\n%prefix% &fKlik hier om terug te keren naar de vorige locatie\n</hover></click>"
|
||||
cannot-spectate-return: "%prefix% &cJe kunt dit alleen doen nadat je een speler hebt bekeken"
|
||||
cannot-run-on-self: "%prefix% &cJe kunt dit commando niet op jezelf gebruiken!"
|
||||
|
||||
@@ -14,6 +14,7 @@ reloaded: "%prefix% &fConfig has been reloaded."
|
||||
reload-failed: "%prefix% &cFailed to reload config."
|
||||
player-not-found: "%prefix% &cJogador não encontrado!"
|
||||
player-not-this-server: "%prefix% &cO jogador não está no servidor!"
|
||||
sender-not-found: "%prefix% &cVocê não pode usar este comando se estiver isento!"
|
||||
spectate-return: "<click:run_command:/grim stopspectating><hover:show_text:\"/grim stopspectating\">\n%prefix% &fClique aqui para retornar a localização anterior\n</hover></click>"
|
||||
cannot-spectate-return: "%prefix% &cVocê só pode fazer isso após espectar um jogador."
|
||||
cannot-run-on-self: "%prefix% &cVocê não pode usar esse comando em sí mesmo!"
|
||||
|
||||
@@ -14,6 +14,7 @@ reloaded: "%prefix% &fConfig has been reloaded."
|
||||
reload-failed: "%prefix% &cFailed to reload config."
|
||||
player-not-found: "%prefix% &cИгрок исключен или находится вне сети!"
|
||||
player-not-this-server: "%prefix% &cИгрок не находится на этом сервере!"
|
||||
sender-not-found: "%prefix% &cВы не можете использовать эту команду, если у вас есть исключение!"
|
||||
spectate-return: "<click:run_command:/grim stopspectating><hover:show_text:\"/grim stopspectating\">\n%prefix% &fНажмите здесь, чтобы вернуться на предыдущее место\n</hover></click>"
|
||||
cannot-spectate-return: "%prefix% &cВы можете сделать это только после наблюдения за игроком"
|
||||
cannot-run-on-self: "%prefix% &cВы не можете использовать эту команду на себе!"
|
||||
|
||||
@@ -14,6 +14,7 @@ reloaded: "%prefix% &fConfig has been reloaded."
|
||||
reload-failed: "%prefix% &cFailed to reload config."
|
||||
player-not-found: "%prefix% &cOyuncu bulunamadı veya aktif değil!"
|
||||
player-not-this-server: "%prefix% &cBu oyuncu sunucuda değil!"
|
||||
sender-not-found: "%prefix% &cMuafsanız bu komutu kullanamazsınız!"
|
||||
spectate-return: "<click:run_command:/grim stopspectating><hover:show_text:\"/grim stopspectating\">\n%prefix% &fBir önceki konuma dönmek için buraya tıklayın.\n</hover></click>"
|
||||
cannot-spectate-return: "%prefix% &cBunu yalnızca bir oyuncuyu izledikten sonra yapabilirsiniz."
|
||||
cannot-run-on-self: "%prefix% &cBu komutu kendi üzerinizde kullanamazsınız!"
|
||||
|
||||
@@ -14,6 +14,7 @@ reloaded: "%prefix% &f配置已经重载完毕."
|
||||
reload-failed: "%prefix% &c重载配置失败."
|
||||
player-not-found: "%prefix% &c指定玩家不存在或离线!"
|
||||
player-not-this-server: "%prefix% &c指定玩家不在此服务器上!"
|
||||
sender-not-found: "%prefix% &c如果你被豁免,则无法使用此命令!"
|
||||
spectate-return: "<click:run_command:/grim stopspectating><hover:show_text:\"/grim stopspectating\">\n%prefix% &f点击这里返回之前的位置\n</hover></click>"
|
||||
cannot-spectate-return: "%prefix% &c您只能在观看玩家后执行此操作"
|
||||
cannot-run-on-self: "%prefix% &c你不能对自己使用此命令!"
|
||||
|
||||
+23
-2
@@ -1,28 +1,41 @@
|
||||
package ac.grim.grimac.platform.fabric.player;
|
||||
|
||||
import ac.grim.grimac.GrimAPI;
|
||||
import ac.grim.grimac.platform.api.entity.GrimEntity;
|
||||
import ac.grim.grimac.platform.api.player.PlatformInventory;
|
||||
import ac.grim.grimac.platform.api.player.PlatformPlayer;
|
||||
import ac.grim.grimac.platform.fabric.GrimACFabricLoaderPlugin;
|
||||
import ac.grim.grimac.platform.fabric.entity.AbstractFabricGrimEntity;
|
||||
import ac.grim.grimac.platform.fabric.utils.convert.FabricConversionUtil;
|
||||
import ac.grim.grimac.utils.common.arguments.CommonGrimArguments;
|
||||
import com.github.retrooper.packetevents.PacketEvents;
|
||||
import com.github.retrooper.packetevents.protocol.player.GameMode;
|
||||
import com.github.retrooper.packetevents.protocol.player.User;
|
||||
import com.github.retrooper.packetevents.util.Vector3d;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
public abstract class AbstractFabricPlatformPlayer extends AbstractFabricGrimEntity implements PlatformPlayer {
|
||||
protected ServerPlayerEntity fabricPlayer;
|
||||
protected final AbstractFabricPlatformInventory inventory;
|
||||
private final @Nullable User user;
|
||||
|
||||
public AbstractFabricPlatformPlayer(ServerPlayerEntity player) {
|
||||
super(player);
|
||||
this.fabricPlayer = player;
|
||||
this.inventory = GrimACFabricLoaderPlugin.LOADER.getPlatformPlayerFactory().getPlatformInventory(player);
|
||||
if (CommonGrimArguments.USE_CHAT_FAST_BYPASS.value()) {
|
||||
Object channel = PacketEvents.getAPI().getProtocolManager().getChannel(fabricPlayer.getUuid());
|
||||
this.user = PacketEvents.getAPI().getProtocolManager().getUser(channel);
|
||||
} else {
|
||||
this.user = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -42,12 +55,20 @@ public abstract class AbstractFabricPlatformPlayer extends AbstractFabricGrimEnt
|
||||
|
||||
@Override
|
||||
public void sendMessage(String message) {
|
||||
fabricPlayer.sendMessage(GrimACFabricLoaderPlugin.LOADER.getFabricMessageUtils().textLiteral(message), false);
|
||||
if (CommonGrimArguments.USE_CHAT_FAST_BYPASS.value() && user != null) {
|
||||
user.sendMessage(message);
|
||||
} else {
|
||||
fabricPlayer.sendMessage(GrimACFabricLoaderPlugin.LOADER.getFabricMessageUtils().textLiteral(message), false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(Component message) {
|
||||
fabricPlayer.sendMessage(GrimACFabricLoaderPlugin.LOADER.getFabricConversionUtil().toNativeText(message), false);
|
||||
if (CommonGrimArguments.USE_CHAT_FAST_BYPASS.value() && user != null) {
|
||||
user.sendMessage(message);
|
||||
} else {
|
||||
fabricPlayer.sendMessage(GrimACFabricLoaderPlugin.LOADER.getFabricConversionUtil().toNativeText(message), false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
# Copyright © 2015 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -114,7 +114,6 @@ case "$( uname )" in #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
@@ -172,7 +171,6 @@ fi
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
@@ -212,8 +210,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
|
||||
Vendored
+1
-2
@@ -70,11 +70,10 @@ goto fail
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@ geyser-base-api = "1.0.2"
|
||||
grim-api = "1.2.0.0"
|
||||
packetevents = "2.10.0+cca07b3-SNAPSHOT"
|
||||
placeholderapi = "2.11.6"
|
||||
via-version = "5.0.4"
|
||||
viaversion = "5.5.0"
|
||||
|
||||
# --- General Java Utilities ---
|
||||
fastutil = "8.5.15"
|
||||
@@ -52,7 +52,7 @@ grim-api = { group = "ac.grim.grimac", name = "GrimAPI", version.ref = "grim-api
|
||||
packetevents-api = { group = "com.github.retrooper", name = "packetevents-api", version.ref = "packetevents" }
|
||||
packetevents-fabric = { group = "com.github.retrooper", name = "packetevents-fabric", version.ref = "packetevents" }
|
||||
packetevents-spigot = { group = "com.github.retrooper", name = "packetevents-spigot", version.ref = "packetevents"}
|
||||
via-version-api = { group = "com.viaversion", name = "viaversion-api", version.ref = "via-version" }
|
||||
viaversion = { group = "com.viaversion", name = "viaversion", version.ref = "viaversion" }
|
||||
|
||||
# --- General Java Utilities ---
|
||||
fastutil = { group = "it.unimi.dsi", name = "fastutil", version.ref = "fastutil"}
|
||||
|
||||
Reference in new issue
Block a user