Compare commits

..
Author SHA1 Message Date
Axionize bf80d63c4a Address code review concerns 2025-10-31 14:14:55 -04:00
Axionize 540fc454b9 Minor code review changes 2025-10-31 07:54:56 -04:00
Axionize cad28d6342 Merge remote-tracking branch 'origin/2.0' into optimize/vector-objects
# Conflicts:
#	common/src/main/java/ac/grim/grimac/utils/data/ReachInterpolationData.java
2025-10-30 13:29:09 -04:00
Axionize 978cbb293c Address more code review concerns 2025-10-30 13:27:43 -04:00
Axionize 42ce2122a8 Apply code review suggestions for Reachutils, BlockModifications, and CheckManagerListener 2025-10-30 13:18:59 -04:00
Axionize 096b01213c Fix code review concerns for break checks 2025-10-30 13:15:07 -04:00
SamB440 f8a5315fff We do not need high bound if player ticks reliably. 2025-10-28 13:27:54 +00:00
Axionize 22e9a649c5 Make input Vec2d 2025-10-27 17:02:48 -04:00
Axionize 8e470d82bc Primitivify inputs 2025-10-27 17:02:48 -04:00
Axionize de083b7b84 Make VectorData vector use primitives 2025-10-27 17:02:48 -04:00
Axionize 3f86a097de More TeleportData conversions 2025-10-27 17:02:48 -04:00
Axionize 1b3b3d7b1d Optimize Collisions forEach predicates 2025-10-27 17:02:48 -04:00
Axionize 40aa1eadbe Primitize more vehicle teleport code 2025-10-27 17:02:48 -04:00
Axionize fac7d98530 use more primitives for teleports 2025-10-27 17:02:48 -04:00
Axionize b9f16762f1 Make BlockBreak object use primitives 2025-10-27 17:02:48 -04:00
Axionize 441af405b7 Optimize usage of vector3i.getSerializedPosition() 2025-10-27 17:02:48 -04:00
Axionize 71fb934a7e Improve performance of updateBlock() 2025-10-27 17:02:48 -04:00
Axionize fe1d3b2b93 Fix minor bug and cleanup primitiiveify ReachInterpolationData 2025-10-27 17:02:48 -04:00
Axionize a408946c0e De Vector-ify TrackedPosition 2025-10-27 17:02:48 -04:00
Axionize f1192069ae Optimize MainSupportingBlockPosFinder 2025-10-27 17:02:48 -04:00
Axionize 09139062c6 Optimize VehiclePositionUpdate, PositionUpdate, and some VectorUtils usage 2025-10-27 17:02:48 -04:00
Axionize 813551a093 Optimize TeleportData 2025-10-27 17:02:47 -04:00
Axionize 3028b4d274 Improve Vector allocations in Reach, BlockPos, ReachUtils 2025-10-27 17:02:47 -04:00
Axionize 7daaca95eb More vector optimizations 2025-10-27 17:02:47 -04:00
AnnotationVisitor 102b5842e7 Add copper golem statue, shelf collision boxes and other missing 1.21.10 stuff (#2315)
* add copper golem statue

* Include ViaVersion substitution

* add shelves

* also add copper armor

* add mannequin box sizes

* Add copper golem size
2025-10-27 11:17:09 -06:00
ManInMyVan a76bf9ce09 add copper golem statue collision box 2025-10-26 19:01:29 -06:00
ManInMyVan 4f3e715f94 add shelf collision box 2025-10-26 18:41:25 -06:00
ManInMyVan 1b47e31776 allow up to one PLAYER_INPUT per tick (#2306) 2025-10-26 11:54:15 -06:00
BT (calcastor/mame) b69906f809 Clear active item before updating inventory on legacy (#2317)
* Apply spotless

* Clear active item before updating inventory on legacy
2025-10-23 22:56:12 -06:00
ManInMyVan 9ba4052c35 possibly fix build 2025-10-23 00:51:27 -06:00
ManInMyVan a86bf57a39 mark MultiActionsD as stable 2025-10-23 00:49:50 -06:00
ManInMyVan a82f2b888a mark MultiActionsC as stable 2025-10-23 00:49:23 -06:00
ManInMyVan 89cf181772 mark PositionBreakB as stable 2025-10-23 00:43:47 -06:00
ManInMyVan 6e5ec332dd remove double space in ChatB 2025-10-23 00:40:59 -06:00
ManInMyVan 79bf7e4fd1 mark ElytraF as stable 2025-10-23 00:39:32 -06:00
ManInMyVan d0aafdd9f7 mark ElytraA as stable 2025-10-22 18:06:48 -06:00
ManInMyVan 3f19abc859 mark ElytraC as stable 2025-10-22 18:06:29 -06:00
ManInMyVan 8a6863b9d3 mark ElytraB as stable 2025-10-22 17:47:05 -06:00
Axionize 26492fd8e3 Optimize new Vector3dm() multiply calls 2025-10-20 23:23:39 -04:00
Axionize 15e809835b Optimize new object creation on vector add() calls 2025-10-20 23:15:23 -04:00
Axionize 1eae5484c7 Micro optimize DefaultResyncHandler 2025-10-20 22:56:11 -04:00
Axionize 90036e2bf2 Optimize Messages (#2309)
* Optimize MessageUtil.replacePlaceHolders()

* Send user messages at packet level bypassing backend server for performance

* Stash temporary changes

* Make Grim send fast chat messages bypasses platform by default
2025-10-20 22:16:21 -04:00
Axionize faf2fb3afb Refactor Grim startup/system args handling 2025-10-20 21:48:49 -04:00
cyramek 7461aca13f fix offground precision loss <= 1.20.4
we're now in 1e-15 average
2025-10-20 19:26:05 +02:00
AoElite f381805cad add translations for the sender-not-found message 2025-10-20 12:25:34 -04:00
AoElite 2fa022eb00 fix GrimDebug command exception if the sender is exempt or not found 2025-10-20 12:25:10 -04:00
cyramek 44900e584b support copper variants of chains 2025-10-20 10:06:45 +02:00
cyramek a2f4dc1979 get rid of a buffer in vector format conversion 2025-10-19 09:18:29 +02:00
Cyramek 0237f956ff [1.21.10] Rewrite block effects, LpVector support, partially implemented collision boxes (#2300) 2025-10-18 11:24:15 +02:00
ManInMyVan e222df8e87 check if we're already sending a bundle packet 2025-10-15 16:50:39 -06:00
ManInMyVan bbab7e222c require CHUNK_BATCH_ACK be sent only when it's supposed to be sent 2025-10-15 15:26:40 -06:00
Axionize 41c7e41a0e Fix Fabric Auto JiJ script 2025-10-15 12:44:15 -04:00
Axionize 5551ef68e1 More fastutil micro optimizations 2025-10-14 19:40:14 -04:00
Axionize ca6e83de4d Use FastUtil Map for violations 2025-10-14 19:37:00 -04:00
Axionize 4d6ac75c73 Fix ClassNotFoundException with new ChatB Via rewriter hook 2025-10-14 17:52:00 -04:00
ManInMyVan 1317ae0bb4 add 2 more rules to .editorconfig 2025-10-14 13:40:20 -06:00
ManInMyVan d538c59a79 CHUNK_BATCH_ACK is async 2025-10-12 15:43:52 -06:00
ManInMyVan 6a223a30c7 don't specify baby scale factor twice 2025-10-12 12:58:00 -06:00
cyramek 2e1c79b4e3 correct bounding box calculations for baby turtles, happy ghasts, dolphins, armadillos, and camels 2025-10-12 20:21:58 +02:00
SamB440 bb143d7993 cancelLerp fixed in 1.21.9, apparently 2025-10-12 17:58:14 +01:00
SamB440 322d2567c0 Bump Gradle to 9.1.0 2025-10-12 17:44:58 +01:00
AoElite 8d7a4cce87 ignore unhittable entities for block placements 2025-10-12 01:21:02 -04:00
ManInMyVan 832194a1e9 fix #2294 2025-10-11 18:46:37 -06:00
ManInMyVan 0321e0bceb fix some warnings 2025-10-11 12:32:57 -06:00
ManInMyVan 63854605b6 slightly more configurable webhook embed 2025-10-11 12:11:03 -06:00
AoElite 5c4602188e allow the embed images to be customizable and show grim's version in the footer 2025-10-11 08:55:29 -04:00
AoElite 163e120f00 any 200 status codes are acceptable 2025-10-11 08:22:32 -04:00
AoElite ce6e1d3e9f ensure the short commit is used 2025-10-11 08:22:02 -04:00
138 changed files with 3531 additions and 1298 deletions

No files matched your search

+2
View File
@@ -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))
}
/**
@@ -6,6 +6,8 @@ import ac.grim.grimac.platform.bukkit.utils.convert.BukkitConversionUtils;
import ac.grim.grimac.platform.bukkit.utils.reflection.PaperUtils;
import ac.grim.grimac.platform.bukkit.world.BukkitPlatformWorld;
import ac.grim.grimac.utils.math.Location;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import org.bukkit.entity.Entity;
import org.jetbrains.annotations.NotNull;
@@ -15,6 +17,8 @@ import java.util.concurrent.CompletableFuture;
public class BukkitGrimEntity implements GrimEntity {
protected static final boolean CAN_USE_DIRECT_GETTERS = PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_20_1) && PaperUtils.PAPER;
private final Entity entity;
private BukkitPlatformWorld bukkitPlatformWorld;
@@ -75,4 +79,26 @@ public class BukkitGrimEntity implements GrimEntity {
location.getPitch()
);
}
@Override
public double distanceSquared(double oX, double oY, double oZ) {
if (CAN_USE_DIRECT_GETTERS) {
double x = this.entity.getX();
double y = this.entity.getY();
double z = this.entity.getZ();
double distX = (x - oX) * (x - oX);
double distY = (y - oY) * (y - oY);
double distZ = (z - oZ) * (z - oZ);
return distX + distY + distZ;
} else {
org.bukkit.Location location = this.entity.getLocation();
double x = location.getX();
double y = location.getY();
double z = location.getZ();
double distX = (x - oX) * (x - oX);
double distY = (y - oY) * (y - oY);
double distZ = (z - oZ) * (z - oZ);
return distX + distY + distZ;
}
}
}
@@ -5,9 +5,8 @@ import ac.grim.grimac.platform.bukkit.utils.convert.BukkitConversionUtils;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
import ac.grim.grimac.utils.data.PistonData;
import ac.grim.grimac.utils.data.TrackedPosition;
import com.github.retrooper.packetevents.protocol.world.BlockFace;
import com.github.retrooper.packetevents.util.Vector3d;
import com.github.retrooper.packetevents.util.Vector3i;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.event.EventHandler;
@@ -28,10 +27,11 @@ public class PistonEvent implements Listener {
private static final double MAX_VERTICAL_DISTANCE = 64.0;
// accuracy isn't that important, it's close enough and performant
private static boolean isCloseEnough(Vector3i vectorA, Vector3d vectorB) {
return Math.abs(vectorA.getX() - vectorB.getX()) <= MAX_HORIZONTAL_DISTANCE
&& Math.abs(vectorA.getY() - vectorB.getY()) <= MAX_VERTICAL_DISTANCE
&& Math.abs(vectorA.getZ() - vectorB.getZ()) <= MAX_HORIZONTAL_DISTANCE;
private static boolean isCloseEnough(final int aX, final int aY, final int aZ,
final double bX, final double bY, final double bZ) {
return Math.abs(aX - bX) <= MAX_HORIZONTAL_DISTANCE
&& Math.abs(aY - bY) <= MAX_VERTICAL_DISTANCE
&& Math.abs(aZ - bZ) <= MAX_HORIZONTAL_DISTANCE;
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
@@ -71,10 +71,13 @@ public class PistonEvent implements Listener {
final int chunkX = event.getBlock().getX() >> 4;
final int chunkZ = event.getBlock().getZ() >> 4;
final BlockFace blockFace = BukkitConversionUtils.fromBukkitFace(event.getDirection());
final Vector3i sourcePos = new Vector3i(piston.getX(), piston.getY(), piston.getZ());
final int sourceX = piston.getX();
final int sourceY = piston.getY();
final int sourceZ = piston.getZ();
for (GrimPlayer player : GrimAPI.INSTANCE.getPlayerDataManager().getEntries()) {
if (player.compensatedWorld.isChunkLoaded(chunkX, chunkZ) && isCloseEnough(sourcePos, player.compensatedEntities.self.trackedServerPosition.getPos())) {
TrackedPosition position = player.compensatedEntities.self.trackedServerPosition;
if (player.compensatedWorld.isChunkLoaded(chunkX, chunkZ) && isCloseEnough(sourceX, sourceY, sourceZ, position.getX(), position.getY(), position.getZ())) {
final int lastTrans = player.lastTransactionSent.get();
PistonData data = new PistonData(blockFace, boxes, lastTrans, true, hasSlimeBlock, hasHoneyBlock);
player.latencyUtils.addRealTimeTaskAsync(lastTrans, () -> player.compensatedWorld.activePistons.add(data));
@@ -127,12 +130,16 @@ public class PistonEvent implements Listener {
}
}
final int chunkX = event.getBlock().getX() >> 4;
final int chunkZ = event.getBlock().getZ() >> 4;
Vector3i sourcePos = new Vector3i(event.getBlock().getX(), event.getBlock().getY(), event.getBlock().getZ());
final Block block = event.getBlock();
final int chunkX = block.getX() >> 4;
final int chunkZ = block.getZ() >> 4;
final int sourceX = block.getX();
final int sourceY = block.getY();
final int sourceZ = block.getZ();
for (GrimPlayer player : GrimAPI.INSTANCE.getPlayerDataManager().getEntries()) {
if (player.compensatedWorld.isChunkLoaded(chunkX, chunkZ) && isCloseEnough(sourcePos, player.compensatedEntities.self.trackedServerPosition.getPos())) {
TrackedPosition position = player.compensatedEntities.self.trackedServerPosition;
if (player.compensatedWorld.isChunkLoaded(chunkX, chunkZ) && isCloseEnough(sourceX, sourceY, sourceZ, position.getX(), position.getY(), position.getZ())) {
int lastTrans = player.lastTransactionSent.get();
PistonData data = new PistonData(face, boxes, lastTrans, false, hasSlimeBlock, hasHoneyBlock);
player.latencyUtils.addRealTimeTaskAsync(lastTrans, () -> player.compensatedWorld.activePistons.add(data));
@@ -177,10 +177,11 @@ public class BukkitItemResetHandler implements ItemResetHandler {
if (legacy) { // 1.8.8
resetItemUsage = player -> {
try {
method.invoke(getHandle.invoke(player));
// in 1.8 we need to resync item usage manually,
// only do so if the player is using an item
if (isUsingItem.test(player)) player.updateInventory();
method.invoke(getHandle.invoke(player));
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
@@ -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
@@ -92,8 +112,12 @@ public class BukkitPlatformPlayer extends BukkitGrimEntity implements PlatformPl
@Override
public Vector3d getPosition() {
org.bukkit.Location location = this.bukkitPlayer.getLocation();
return new Vector3d(location.getX(), location.getY(), location.getZ());
if (CAN_USE_DIRECT_GETTERS) {
return new Vector3d(this.bukkitPlayer.getX(), this.bukkitPlayer.getY(), this.bukkitPlayer.getZ());
} else {
org.bukkit.Location location = this.bukkitPlayer.getLocation();
return new Vector3d(location.getX(), location.getY(), location.getZ());
}
}
@Override
+1 -1
View File
@@ -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
@@ -0,0 +1,32 @@
package ac.grim.grimac.checks.impl.badpackets;
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.protocol.packettype.PacketType;
@CheckData(name = "BadPacketsZ", experimental = true)
public class BadPacketsZ extends Check implements PacketCheck {
private boolean sent;
public BadPacketsZ(GrimPlayer player) {
super(player);
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.CLIENT_TICK_END) {
sent = false;
}
if (event.getPacketType() == PacketType.Play.Client.PLAYER_INPUT) {
if (sent) {
flagAndAlert();
}
sent = true;
}
}
}
@@ -20,8 +20,8 @@ public class AirLiquidBreak extends Check implements BlockBreakCheck {
private int lastTick;
private boolean didLastFlag;
// Initialize to non-null values to prevent NPE when checking for blockType properties and if position equals old position
private @NotNull Vector3i lastBreakLoc = new Vector3i();
private @NotNull StateType lastBlockType = StateTypes.AIR;
private int lastX, lastY, lastZ;
private float lastHardness, lastBlastResistance;
public AirLiquidBreak(GrimPlayer player) {
super(player);
@@ -40,15 +40,20 @@ public class AirLiquidBreak extends Check implements BlockBreakCheck {
// I am explicitly making this patch as narrow and specific as possible to potentially discover other blocks that exhibit similar behaviour
int newTick = GrimAPI.INSTANCE.getTickManager().currentTick;
if (lastTick == newTick
&& lastBreakLoc.equals(blockBreak.position)
&& lastX == blockBreak.x
&& lastY == blockBreak.y
&& lastZ == blockBreak.z
&& !didLastFlag
&& lastBlockType.getHardness() == 0.0F
&& lastBlockType.getBlastResistance() == 0.0F
&& lastHardness == 0.0F
&& lastBlastResistance == 0.0F
&& block == StateTypes.WATER
) return;
lastTick = newTick;
lastBreakLoc = blockBreak.position;
lastBlockType = block;
lastX = blockBreak.x;
lastY = blockBreak.y;
lastZ = blockBreak.z;
lastHardness = block.getHardness();
lastBlastResistance = block.getBlastResistance();
// the block does not have a hitbox
boolean invalid = (block == StateTypes.LIGHT && !(player.inventory.getHeldItem().is(ItemTypes.LIGHT) || player.inventory.getOffHand().is(ItemTypes.LIGHT)))
@@ -25,10 +25,9 @@ public class FarBreak extends Check implements BlockBreakCheck {
double min = Double.MAX_VALUE;
for (double d : player.getPossibleEyeHeights()) {
SimpleCollisionBox box = new SimpleCollisionBox(blockBreak.position);
Vector3dm eyes = new Vector3dm(player.x, player.y + d, player.z);
Vector3dm best = VectorUtils.cutBoxToVector(eyes, box);
min = Math.min(min, eyes.distanceSquared(best));
SimpleCollisionBox box = new SimpleCollisionBox(blockBreak.x, blockBreak.y, blockBreak.z);
Vector3dm best = VectorUtils.cutBoxToVector(player.x, player.y + d, player.z, box);
min = Math.min(min, best.distanceSquared(player.x, player.y + d, player.z));
}
// getPickRange() determines this?
@@ -36,8 +36,11 @@ public class FastBreak extends Check implements BlockBreakCheck {
super(playerData);
}
// The block the player is currently breaking
Vector3i targetBlockPosition = null;
// The block the player is currently breaking, represented by primitives for performance.
// `targetBlockIsSet` tracks nullness of the position, since primitives can't be null
private boolean targetBlockIsSet;
private int targetBlockX, targetBlockY, targetBlockZ;
// The maximum amount of damage the player deals to the block
//
double maximumBlockDamage = 0;
@@ -67,8 +70,11 @@ public class FastBreak extends Check implements BlockBreakCheck {
// * can we translate back "up" to server version and run check against server version to avoid loading older registries?
WrappedBlockState block = clientOlderThanServer ? WrappedBlockState.getByGlobalId(player.getClientVersion(), player.getViaTranslatedClientBlockID(blockBreak.block.getGlobalId())) : blockBreak.block;
startBreak = System.currentTimeMillis() - (targetBlockPosition == null ? 50 : 0); // ???
targetBlockPosition = blockBreak.position;
startBreak = System.currentTimeMillis() - (!targetBlockIsSet ? 50 : 0); // ???
targetBlockIsSet = true;
targetBlockX = blockBreak.x;
targetBlockY = blockBreak.y;
targetBlockZ = blockBreak.z;
maximumBlockDamage = BlockBreakSpeed.getBlockDamage(player, block);
@@ -87,9 +93,7 @@ public class FastBreak extends Check implements BlockBreakCheck {
}
clampBalance();
}
if (blockBreak.action == DiggingAction.FINISHED_DIGGING && targetBlockPosition != null) {
} else if (blockBreak.action == DiggingAction.FINISHED_DIGGING && targetBlockIsSet) {
double predictedTime = Math.ceil(1 / maximumBlockDamage) * 50;
double realTime = System.currentTimeMillis() - startBreak;
double diff = predictedTime - realTime;
@@ -117,8 +121,8 @@ public class FastBreak extends Check implements BlockBreakCheck {
public void onPacketReceive(PacketReceiveEvent event) {
// Find the most optimal block damage using the animation packet, which is sent at least once a tick when breaking blocks
// On 1.8 clients, via screws with this packet meaning we must fall back to the 1.8 idle flying packet
if ((player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) ? event.getPacketType() == PacketType.Play.Client.ANIMATION : WrapperPlayClientPlayerFlying.isFlying(event.getPacketType())) && targetBlockPosition != null) {
maximumBlockDamage = Math.max(maximumBlockDamage, BlockBreakSpeed.getBlockDamage(player, player.compensatedWorld.getBlock(targetBlockPosition)));
if ((player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) ? event.getPacketType() == PacketType.Play.Client.ANIMATION : WrapperPlayClientPlayerFlying.isFlying(event.getPacketType())) && targetBlockIsSet) {
maximumBlockDamage = Math.max(maximumBlockDamage, BlockBreakSpeed.getBlockDamage(player, player.compensatedWorld.getBlock(targetBlockX, targetBlockY, targetBlockZ)));
}
}
@@ -21,7 +21,9 @@ public class MultiBreak extends Check implements BlockBreakCheck {
private final List<String> flags = new ArrayList<>();
private boolean hasBroken;
private BlockFace lastFace;
private Vector3i lastPos;
private int lastX;
private int lastY;
private int lastZ;
public MultiBreak(GrimPlayer player) {
super(player);
@@ -33,10 +35,22 @@ public class MultiBreak extends Check implements BlockBreakCheck {
return;
}
if (hasBroken && (blockBreak.face != lastFace || !blockBreak.position.equals(lastPos))) {
final int currentX = blockBreak.x;
final int currentY = blockBreak.y;
final int currentZ = blockBreak.z;
if (hasBroken && (blockBreak.face != lastFace ||
currentX != lastX ||
currentY != lastY ||
currentZ != lastZ)) {
final String currentPosString = MessageUtil.toUnlabeledString(currentX, currentY, currentZ);
final String lastPosString = MessageUtil.toUnlabeledString(lastX, lastY, lastZ);
final String verbose = "face=" + blockBreak.face + ", lastFace=" + lastFace
+ ", pos=" + MessageUtil.toUnlabledString(blockBreak.position)
+ ", lastPos=" + MessageUtil.toUnlabledString(lastPos);
+ ", pos=" + currentPosString
+ ", lastPos=" + lastPosString;
if (!player.canSkipTicks()) {
if (flagAndAlert(verbose) && shouldModifyPackets()) {
blockBreak.cancel();
@@ -47,7 +61,9 @@ public class MultiBreak extends Check implements BlockBreakCheck {
}
lastFace = blockBreak.face;
lastPos = blockBreak.position;
lastX = currentX;
lastY = currentY;
lastZ = currentZ;
hasBroken = true;
}
@@ -9,7 +9,7 @@ import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
import com.github.retrooper.packetevents.protocol.world.BlockFace;
@CheckData(name = "PositionBreakB", experimental = true)
@CheckData(name = "PositionBreakB")
public class PositionBreakB extends Check implements BlockBreakCheck {
private final int releaseFace = player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_8) ? 0 : 255;
private BlockFace lastFace;
@@ -7,6 +7,7 @@ import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.BlockBreak;
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
import ac.grim.grimac.utils.data.Pair;
import ac.grim.grimac.utils.data.TeleportData;
import ac.grim.grimac.utils.math.Vector3dm;
import ac.grim.grimac.utils.nmsutil.Ray;
import ac.grim.grimac.utils.nmsutil.ReachUtils;
@@ -14,6 +15,7 @@ import com.github.retrooper.packetevents.protocol.attribute.Attributes;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
import com.github.retrooper.packetevents.protocol.player.GameMode;
import com.github.retrooper.packetevents.protocol.teleport.RelativeFlag;
import com.github.retrooper.packetevents.protocol.world.BlockFace;
import com.github.retrooper.packetevents.util.Vector3d;
import com.github.retrooper.packetevents.util.Vector3f;
@@ -70,7 +72,7 @@ public class RotationBreak extends Check implements BlockBreakCheck {
}
private boolean didRayTraceHit(BlockBreak blockBreak) {
SimpleCollisionBox box = new SimpleCollisionBox(blockBreak.position);
SimpleCollisionBox box = new SimpleCollisionBox(blockBreak.x, blockBreak.y, blockBreak.z);
final double[] possibleEyeHeights = player.getPossibleEyeHeights();
@@ -111,7 +113,8 @@ public class RotationBreak extends Check implements BlockBreakCheck {
for (Vector3f lookDir : possibleLookDirs) {
Vector3d starting = new Vector3d(player.x, player.y + d, player.z);
Ray trace = new Ray(player, starting.getX(), starting.getY(), starting.getZ(), lookDir.getX(), lookDir.getY());
Pair<Vector3dm, BlockFace> intercept = ReachUtils.calculateIntercept(box, trace.getOrigin(), trace.getPointAtDistance(distance));
Vector3dm pointAtDistance = trace.getPointAtDistance(distance);
Pair<Vector3dm, BlockFace> intercept = ReachUtils.calculateIntercept(box, trace.getOriginX(), trace.getOriginY(), trace.getOriginZ(), pointAtDistance.getX(), pointAtDistance.getY(), pointAtDistance.getZ());
if (intercept.first() != null) return true;
}
@@ -19,7 +19,14 @@ import static ac.grim.grimac.utils.nmsutil.BlockBreakSpeed.getBlockDamage;
public class WrongBreak extends Check implements BlockBreakCheck {
private final int exemptedY = player.getClientVersion().isOlderThan(ClientVersion.V_1_8) ? 255 : (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_14) ? -1 : 4095);
private boolean lastBlockWasInstantBreak = false;
private Vector3i lastBlock, lastCancelledBlock, lastLastBlock = null;
private boolean lastBlockIsSet;
private int lastBlockX, lastBlockY, lastBlockZ;
private boolean lastCancelledBlockIsSet;
private int lastCancelledBlockX, lastCancelledBlockY, lastCancelledBlockZ;
private boolean lastLastBlockIsSet;
public WrongBreak(final GrimPlayer player) {
super(player);
@@ -27,8 +34,7 @@ public class WrongBreak extends Check implements BlockBreakCheck {
// The client sometimes sends a wierd cancel packet
private boolean shouldExempt(final WrappedBlockState block, int yPos) {
// lastLastBlock is always null when this happens, and lastBlock isn't
if (lastLastBlock != null || lastBlock == null)
if (lastLastBlockIsSet || !lastBlockIsSet)
return false;
// on pre 1.14.4 clients, the YPos of this packet is always the same
@@ -41,22 +47,29 @@ public class WrongBreak extends Check implements BlockBreakCheck {
@Override
public void onBlockBreak(BlockBreak blockBreak) {
final int x = blockBreak.x;
final int y = blockBreak.y;
final int z = blockBreak.z;
if (blockBreak.action == DiggingAction.START_DIGGING) {
final Vector3i pos = blockBreak.position;
lastBlockWasInstantBreak = getBlockDamage(player, blockBreak.block) >= 1;
lastCancelledBlock = null;
lastLastBlock = lastBlock;
lastBlock = pos;
}
lastCancelledBlockIsSet = false;
if (blockBreak.action == DiggingAction.CANCELLED_DIGGING) {
final Vector3i pos = blockBreak.position;
lastLastBlockIsSet = lastBlockIsSet;
if (!shouldExempt(blockBreak.block, pos.y) && !pos.equals(lastBlock)) {
lastBlockIsSet = true;
lastBlockX = x;
lastBlockY = y;
lastBlockZ = z;
} else if (blockBreak.action == DiggingAction.CANCELLED_DIGGING) {
boolean notLastBlock = !lastBlockIsSet || x != lastBlockX || y != lastBlockY || z != lastBlockZ;
if (!shouldExempt(blockBreak.block, y) && notLastBlock) {
// https://github.com/GrimAnticheat/Grim/issues/1512
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_14_4) || (!lastBlockWasInstantBreak && pos.equals(lastCancelledBlock))) {
if (flagAndAlert("action=CANCELLED_DIGGING" + ", last=" + MessageUtil.toUnlabledString(lastBlock) + ", pos=" + MessageUtil.toUnlabledString(pos))) {
boolean isLastCancelled = lastCancelledBlockIsSet && x == lastCancelledBlockX && y == lastCancelledBlockY && z == lastCancelledBlockZ;
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_14_4) || (!lastBlockWasInstantBreak && isLastCancelled)) {
String lastBlockStr = lastBlockIsSet ? MessageUtil.toUnlabeledString(lastBlockX, lastBlockY, lastBlockZ) : "null";
if (flagAndAlert("action=CANCELLED_DIGGING" + ", last=" + lastBlockStr + ", pos=" + MessageUtil.toUnlabeledString(x, y, z))) {
if (shouldModifyPackets()) {
blockBreak.cancel();
}
@@ -64,18 +77,20 @@ public class WrongBreak extends Check implements BlockBreakCheck {
}
}
lastCancelledBlock = pos;
lastLastBlock = null;
lastBlock = null;
return;
}
lastCancelledBlockIsSet = true;
lastCancelledBlockX = x;
lastCancelledBlockY = y;
lastCancelledBlockZ = z;
if (blockBreak.action == DiggingAction.FINISHED_DIGGING) {
final Vector3i pos = blockBreak.position;
lastLastBlockIsSet = lastBlockIsSet = false;
} else if (blockBreak.action == DiggingAction.FINISHED_DIGGING) {
boolean notLastCancelled = !lastCancelledBlockIsSet || x != lastCancelledBlockX || y != lastCancelledBlockY || z != lastCancelledBlockZ;
boolean notLastBlock = !lastBlockIsSet || x != lastBlockX || y != lastBlockY || z != lastBlockZ;
// when a player looks away from the mined block, they send a cancel, and if they look at it again, they don't send another start. (thanks mojang!)
if (!pos.equals(lastCancelledBlock) && (!lastBlockWasInstantBreak || player.getClientVersion().isOlderThan(ClientVersion.V_1_14_4)) && !pos.equals(lastBlock)) {
if (flagAndAlert("action=FINISHED_DIGGING" + ", last=" + MessageUtil.toUnlabledString(lastBlock) + ", pos=" + MessageUtil.toUnlabledString(pos))) {
if (notLastCancelled && (!lastBlockWasInstantBreak || player.getClientVersion().isOlderThan(ClientVersion.V_1_14_4)) && notLastBlock) {
String lastBlockStr = lastBlockIsSet ? MessageUtil.toUnlabeledString(lastBlockX, lastBlockY, lastBlockZ) : "null";
if (flagAndAlert("action=FINISHED_DIGGING" + ", last=" + lastBlockStr + ", pos=" + MessageUtil.toUnlabeledString(x, y, z))) {
if (shouldModifyPackets()) {
blockBreak.cancel();
}
@@ -84,9 +99,7 @@ public class WrongBreak extends Check implements BlockBreakCheck {
// 1.14.4+ clients don't send another start break in protected regions
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_14_4)) {
lastCancelledBlock = null;
lastLastBlock = null;
lastBlock = null;
lastCancelledBlockIsSet = lastLastBlockIsSet = lastBlockIsSet = false;
}
}
}
@@ -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;
@@ -24,11 +26,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 +52,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();
}
}
}
@@ -143,7 +143,10 @@ public class Reach extends Check implements PacketCheck {
} else {
SimpleCollisionBox targetBox = reachEntity.getPossibleCollisionBoxes();
if (reachEntity.type == EntityTypes.END_CRYSTAL) {
targetBox = new SimpleCollisionBox(reachEntity.trackedServerPosition.getPos().subtract(1, 0, 1), reachEntity.trackedServerPosition.getPos().add(1, 2, 1));
final double x = reachEntity.trackedServerPosition.getX();
final double y = reachEntity.trackedServerPosition.getX();
final double z = reachEntity.trackedServerPosition.getX();
targetBox = new SimpleCollisionBox(x - 1, y, z - 1, x + 1, y + 2, z + 1);
}
return ReachUtils.getMinReachToBox(player, targetBox) > player.compensatedEntities.self.getAttributeValue(Attributes.ENTITY_INTERACTION_RANGE);
}
@@ -175,7 +178,10 @@ public class Reach extends Check implements PacketCheck {
SimpleCollisionBox targetBox = reachEntity.getPossibleCollisionBoxes();
if (reachEntity.type == EntityTypes.END_CRYSTAL) { // Hardcode end crystal box
targetBox = new SimpleCollisionBox(reachEntity.trackedServerPosition.getPos().subtract(1, 0, 1), reachEntity.trackedServerPosition.getPos().add(1, 2, 1));
final double x = reachEntity.trackedServerPosition.getX();
final double y = reachEntity.trackedServerPosition.getX();
final double z = reachEntity.trackedServerPosition.getX();
targetBox = new SimpleCollisionBox(x - 1, y, z - 1, x + 1, y + 2, z + 1);
}
// 1.7 and 1.8 players get a bit of extra hitbox (this is why you should use 1.8 on cross version servers)
@@ -218,21 +224,27 @@ public class Reach extends Check implements PacketCheck {
// +3 would be 3 + 3 = 6, which is the pre-1.20.5 behaviour, preventing "Missed Hitbox"
final double distance = maxReach + 3;
final double[] possibleEyeHeights = player.getPossibleEyeHeights();
final Vector3dm eyePos = new Vector3dm(from.getX(), 0, from.getZ());
final double eyePosX = from.getX();
final double eyePosZ = from.getZ();
for (Vector3dm lookVec : possibleLookDirs) {
for (double eye : possibleEyeHeights) {
eyePos.setY(from.getY() + eye);
Vector3dm endReachPos = eyePos.clone().add(new Vector3dm(lookVec.getX() * distance, lookVec.getY() * distance, lookVec.getZ() * distance));
final double eyePosY = from.getY() + eye;
Vector3dm intercept = ReachUtils.calculateIntercept(targetBox, eyePos, endReachPos).first();
final double endX = eyePosX + lookVec.getX() * distance;
final double endY = eyePosY + lookVec.getY() * distance;
final double endZ = eyePosZ + lookVec.getZ() * distance;
if (ReachUtils.isVecInside(targetBox, eyePos)) {
Vector3dm intercept = ReachUtils.calculateIntercept(targetBox, eyePosX, eyePosY, eyePosZ, endX, endY, endZ).first();
if (ReachUtils.isVecInside(targetBox, eyePosX, eyePosY, eyePosZ)) {
minDistance = 0;
break;
}
if (intercept != null) {
minDistance = Math.min(eyePos.distance(intercept), minDistance);
minDistance = Math.min(intercept.distance(eyePosX, eyePosY, eyePosZ), minDistance);
}
}
}
@@ -10,7 +10,7 @@ import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
@CheckData(name = "ElytraA", description = "Started gliding while already gliding", experimental = true)
@CheckData(name = "ElytraA", description = "Started gliding while already gliding")
public class ElytraA extends Check implements PostPredictionCheck {
private boolean setback;
@@ -9,7 +9,7 @@ import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
@CheckData(name = "ElytraB", description = "Started gliding without jumping", experimental = true)
@CheckData(name = "ElytraB", description = "Started gliding without jumping")
public class ElytraB extends Check implements PostPredictionCheck {
private boolean glide;
private boolean setback;
@@ -11,7 +11,7 @@ import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.GameMode;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
@CheckData(name = "ElytraC", description = "Started gliding too frequently", experimental = true)
@CheckData(name = "ElytraC", description = "Started gliding too frequently")
public class ElytraC extends Check implements PostPredictionCheck {
private boolean glideThisTick, glideLastTick, setback;
private int flags;
@@ -10,7 +10,7 @@ import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
@CheckData(name = "ElytraF", description = "Started gliding while on ground", experimental = true)
@CheckData(name = "ElytraF", description = "Started gliding while on ground")
public class ElytraF extends Check implements PostPredictionCheck {
private boolean setback;
@@ -15,6 +15,8 @@ public class VehiclePredictionRunner extends Check implements VehicleCheck {
public void process(final VehiclePositionUpdate vehicleUpdate) {
// Vehicle onGround = false always
// We don't do vehicle setbacks because vehicle netcode sucks.
player.movementCheckRunner.processAndCheckMovementPacket(new PositionUpdate(vehicleUpdate.from(), vehicleUpdate.to(), false, null, null, vehicleUpdate.isTeleport()));
player.movementCheckRunner.processAndCheckMovementPacket(new PositionUpdate(
vehicleUpdate.fromX(), vehicleUpdate.fromY(), vehicleUpdate.fromZ(), vehicleUpdate.toX(), vehicleUpdate.toY(), vehicleUpdate.toZ(),
false, null, null, vehicleUpdate.isTeleport()));
}
}
@@ -12,12 +12,13 @@ import org.jetbrains.annotations.NotNull;
import java.util.StringJoiner;
@CheckData(name = "MultiActionsC", description = "Clicked in inventory while moving", experimental = true)
@CheckData(name = "MultiActionsC", description = "Clicked in inventory while moving")
public class MultiActionsC extends Check implements PacketCheck {
public MultiActionsC(GrimPlayer player) {
super(player);
}
// TODO: move this to a bett spot? not sure where to put this
@Contract(pure = true)
public static String getVerbose(@NotNull GrimPlayer player) {
StringJoiner verbose = new StringJoiner(", ");
@@ -7,7 +7,7 @@ import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
@CheckData(name = "MultiActionsD", description = "Closed inventory while moving", experimental = true)
@CheckData(name = "MultiActionsD", description = "Closed inventory while moving")
public class MultiActionsD extends Check implements PacketCheck {
public MultiActionsD(GrimPlayer player) {
super(player);
@@ -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()));
}
}
}
}
@@ -42,24 +42,26 @@ public class DebugHandler extends AbstractDebugHandler implements PostPrediction
double offset = predictionComplete.getOffset();
if (listeners.isEmpty() && !outputToConsole) return;
if (player.predictedVelocity.vector.lengthSquared() == 0 && offset == 0) return;
final double predictedX = player.predictedVelocity.vectorX;
final double predictedY = player.predictedVelocity.vectorY;
final double predictedZ = player.predictedVelocity.vectorZ;
if (predictedX == 0.0 && predictedY == 0.0 && predictedZ == 0.0 && offset == 0) return;
String color = pickColor(offset, offset);
Vector3dm predicted = player.predictedVelocity.vector;
Vector3dm actually = player.actualMovement;
String xColor = pickColor(Math.abs(predicted.getX() - actually.getX()), offset);
String yColor = pickColor(Math.abs(predicted.getY() - actually.getY()), offset);
String zColor = pickColor(Math.abs(predicted.getZ() - actually.getZ()), offset);
String xColor = pickColor(Math.abs(predictedX - actually.getX()), offset);
String yColor = pickColor(Math.abs(predictedY - actually.getY()), offset);
String zColor = pickColor(Math.abs(predictedZ - actually.getZ()), offset);
Component p = Component.empty()
.append(P_PREFIX.color(NamedTextColor.NAMES.value(color)))
.append(Component.text(predicted.getX()).color(NamedTextColor.NAMES.value(xColor)))
.append(Component.text(predictedX).color(NamedTextColor.NAMES.value(xColor)))
.append(Component.space())
.append(Component.text(predicted.getY()).color(NamedTextColor.NAMES.value(yColor)))
.append(Component.text(predictedY).color(NamedTextColor.NAMES.value(yColor)))
.append(Component.space())
.append(Component.text(predicted.getZ()).color(NamedTextColor.NAMES.value(zColor)));
.append(Component.text(predictedZ).color(NamedTextColor.NAMES.value(zColor)));
Component a = Component.empty()
.append(A_PREFIX.color(NamedTextColor.NAMES.value(color)))
@@ -59,13 +59,16 @@ public class AirLiquidPlace extends BlockPlaceCheck {
if (player.gamemode == GameMode.CREATIVE) return;
Vector3i blockPos = place.position;
StateType placeAgainst = player.compensatedWorld.getBlockType(blockPos.getX(), blockPos.getY(), blockPos.getZ());
final int x = blockPos.getX();
final int y = blockPos.getY();
final int z = blockPos.getZ();
StateType placeAgainst = player.compensatedWorld.getBlockType(x, y, z);
int currentTick = GrimAPI.INSTANCE.getTickManager().currentTick;
// this is actual more lenient than we need to be, We can check up to 1 ticks for all changes at location sand up to 0 ticks for first change
// But for such tiny differences in legitness it's not worth it.
Iterable<BlockModification> blockModifications = player.blockHistory.getRecentModifications((blockModification) -> currentTick - blockModification.tick() < 2
&& blockPos.equals(blockModification.location())
&& x == blockModification.x() && y == blockModification.y() && z == blockModification.z()
&& (blockModification.cause() == BlockModification.Cause.START_DIGGING || blockModification.cause() == BlockModification.Cause.HANDLE_NETTY_SYNC_TRANSACTION));
// Check if old block from instant breaking in same tick as the current placement was valid
@@ -30,9 +30,8 @@ public class FarPlace extends BlockPlaceCheck {
final double[] possibleEyeHeights = player.getPossibleEyeHeights();
for (double d : possibleEyeHeights) {
SimpleCollisionBox box = new SimpleCollisionBox(blockPos);
Vector3dm eyes = new Vector3dm(player.x, player.y + d, player.z);
Vector3dm best = VectorUtils.cutBoxToVector(eyes, box);
min = Math.min(min, eyes.distanceSquared(best));
Vector3dm best = VectorUtils.cutBoxToVector(player.x, player.y + d, player.z, box);
min = Math.min(min, best.distanceSquared(player.x, player.y + d, player.z));
}
// getPickRange() determines this?
@@ -112,10 +112,10 @@ public class RotationPlace extends BlockPlaceCheck {
for (double d : possibleEyeHeights) {
for (Vector3f lookDir : possibleLookDirs) {
// x, y, z are correct for the block placement even after post tick because of code elsewhere
Vector3d starting = new Vector3d(player.x, player.y + d, player.z);
// xRot and yRot are a tick behind
Ray trace = new Ray(player, starting.getX(), starting.getY(), starting.getZ(), lookDir.getX(), lookDir.getY());
Pair<Vector3dm, BlockFace> intercept = ReachUtils.calculateIntercept(box, trace.getOrigin(), trace.getPointAtDistance(distance));
Ray trace = new Ray(player, player.x, player.y + d, player.z, lookDir.getX(), lookDir.getY());
Vector3dm pointAtDistance = trace.getPointAtDistance(distance);
Pair<Vector3dm, BlockFace> intercept = ReachUtils.calculateIntercept(box, trace.getOriginX(), trace.getOriginY(), trace.getOriginZ(), pointAtDistance.getX(), pointAtDistance.getY(), pointAtDistance.getZ());
if (intercept.first() != null) return true;
}
@@ -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);
}
@@ -64,11 +64,13 @@ public class KnockbackHandler extends Check implements PostPredictionCheck {
// Blacklist problemated vector until mojang fixes a client-sided bug
if (playerVelocity.getY() == -0.04) {
velocity.setVelocity(playerVelocity.add(new Vector3d(0, 1 / 8000D, 0)));
velocity.setVelocity(playerVelocity.add(0, 1 / 8000D, 0));
playerVelocity = velocity.getVelocity();
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()));
@@ -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;
@@ -483,7 +483,8 @@ public class CheckManagerListener extends PacketListenerAbstract {
player.yaw = move.getYaw();
player.pitch = move.getPitch();
final VehiclePositionUpdate update = new VehiclePositionUpdate(clamp, position, move.getYaw(), move.getPitch(), player.packetStateData.lastPacketWasTeleport);
final VehiclePositionUpdate update = new VehiclePositionUpdate(clamp.getX(), clamp.getY(), clamp.getZ(),
position.getX(), position.getY(), position.getZ(), move.getYaw(), move.getPitch(), player.packetStateData.lastPacketWasTeleport);
player.checkManager.onVehiclePositionUpdate(update);
player.packetStateData.receivedSteerVehicle = false;
@@ -532,14 +533,17 @@ public class CheckManagerListener extends PacketListenerAbstract {
player.onPacketCancel();
}
Vector3i facePos = new Vector3i(packet.getBlockPosition().getX() + packet.getFace().getModX(), packet.getBlockPosition().getY() + packet.getFace().getModY(), packet.getBlockPosition().getZ() + packet.getFace().getModZ());
// Ends the client prediction introduced in 1.19+
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_19) && PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_19)) {
player.user.sendPacket(new WrapperPlayServerAcknowledgeBlockChanges(packet.getSequence()));
} else { // The client isn't smart enough to revert changes
player.resyncPosition(packet.getBlockPosition());
player.resyncPosition(facePos);
final Vector3i blockPosition = packet.getBlockPosition();
final int x = blockPosition.getX();
final int y = blockPosition.getY();
final int z = blockPosition.getZ();
player.resyncPosition(x, y ,z);
final BlockFace face = packet.getFace();
player.resyncPosition(x + face.getModX(), y + face.getModY(), z + face.getModZ());
}
// Stop inventory desync from cancelling place
@@ -599,6 +603,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);
}
@@ -720,9 +728,8 @@ public class CheckManagerListener extends PacketListenerAbstract {
}
if (hasPosition) {
Vector3d position = new Vector3d(x, y, z);
Vector3d clampVector = VectorUtils.clampVector(position);
final PositionUpdate update = new PositionUpdate(new Vector3d(player.x, player.y, player.z), position, onGround, teleportData.getSetback(), teleportData.getTeleportData(), teleportData.isTeleport());
Vector3d clampVector = VectorUtils.clampVector(x, y, z);
final PositionUpdate update = new PositionUpdate(player.x, player.y, player.z, x, y ,z, onGround, teleportData.getSetback(), teleportData.getTeleportData(), teleportData.isTeleport());
// Stupidity doesn't care about 0.03
if (!player.packetStateData.lastPacketWasOnePointSeventeenDuplicate) {
@@ -766,14 +773,15 @@ public class CheckManagerListener extends PacketListenerAbstract {
return;
}
final BlockBreak blockBreak = new BlockBreak(player, packet.getBlockPosition(), packet.getBlockFace(), packet.getBlockFaceId(), action, packet.getSequence(), player.compensatedWorld.getBlock(packet.getBlockPosition()));
final Vector3i blockPosition = packet.getBlockPosition();
final BlockBreak blockBreak = new BlockBreak(player, blockPosition.getX(), blockPosition.getY(), blockPosition.getZ(), packet.getBlockFace(), packet.getBlockFaceId(), action, packet.getSequence(), player.compensatedWorld.getBlock(packet.getBlockPosition()));
player.checkManager.onBlockBreak(blockBreak);
if (blockBreak.isCancelled()) {
event.setCancelled(true);
player.onPacketCancel();
player.resyncPosition(blockBreak.position, packet.getSequence());
player.resyncPosition(blockBreak.x, blockBreak.y, blockBreak.z, packet.getSequence());
return;
}
@@ -781,7 +789,7 @@ public class CheckManagerListener extends PacketListenerAbstract {
if (action == DiggingAction.FINISHED_DIGGING && BREAKABLE.apply(blockBreak.block.getType())) {
player.compensatedWorld.startPredicting();
player.compensatedWorld.updateBlock(blockBreak.position.x, blockBreak.position.y, blockBreak.position.z, 0);
player.compensatedWorld.updateBlock(blockBreak.x, blockBreak.y, blockBreak.z, 0);
player.compensatedWorld.stopPredicting(packet);
}
@@ -791,11 +799,14 @@ public class CheckManagerListener extends PacketListenerAbstract {
// Instant breaking, no damage means it is unbreakable by creative players (with swords)
if (damage >= 1) {
player.compensatedWorld.startPredicting();
final int x = blockBreak.x;
final int y = blockBreak.y;
final int z = blockBreak.z;
player.blockHistory.add(
new BlockModification(
player.compensatedWorld.getBlock(blockBreak.position),
player.compensatedWorld.getBlock(x, y, z),
WrappedBlockState.getByGlobalId(0),
blockBreak.position,
x, y, z,
GrimAPI.INSTANCE.getTickManager().currentTick,
BlockModification.Cause.START_DIGGING
)
@@ -803,9 +814,9 @@ public class CheckManagerListener extends PacketListenerAbstract {
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_13) && Materials.isWaterSource(player.getClientVersion(), blockBreak.block)) {
// Vanilla uses a method to grab water flowing, but as you can't break flowing water
// We can simply treat all waterlogged blocks or source blocks as source blocks
player.compensatedWorld.updateBlock(blockBreak.position, StateTypes.WATER.createBlockState(CompensatedWorld.blockVersion));
player.compensatedWorld.updateBlock(x, y, z, StateTypes.WATER.createBlockState(CompensatedWorld.blockVersion).getGlobalId());
} else {
player.compensatedWorld.updateBlock(blockBreak.position.x, blockBreak.position.y, blockBreak.position.z, 0);
player.compensatedWorld.updateBlock(x, y, z, 0);
}
player.compensatedWorld.stopPredicting(packet);
}
@@ -2,10 +2,9 @@ package ac.grim.grimac.events.packets;
import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.data.Pair;
import ac.grim.grimac.utils.data.RotationData;
import ac.grim.grimac.utils.data.VehicleData;
import ac.grim.grimac.utils.math.GrimMath;
import ac.grim.grimac.utils.math.Location;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.event.PacketListenerAbstract;
import com.github.retrooper.packetevents.event.PacketListenerPriority;
@@ -35,22 +34,26 @@ public class PacketServerTeleport extends PacketListenerAbstract {
WrapperPlayServerPlayerPositionAndLook teleport = new WrapperPlayServerPlayerPositionAndLook(event);
Vector3d pos = new Vector3d(teleport.getX(), teleport.getY(), teleport.getZ());
double posX = teleport.getX();
double posY = teleport.getY();
double posZ = teleport.getZ();
final float yaw = teleport.getYaw();
final float pitch = teleport.getPitch();
// This is the first packet sent to the client which we need to track
if (player.getSetbackTeleportUtil().getRequiredSetBack() == null) {
// Player teleport event gets called AFTER player join event
player.x = teleport.getX();
player.y = teleport.getY();
player.z = teleport.getZ();
player.yaw = teleport.getYaw();
player.pitch = teleport.getPitch();
player.x = posX;
player.y = posY;
player.z = posZ;
player.yaw = yaw;
player.pitch = pitch;
player.lastX = teleport.getX();
player.lastY = teleport.getY();
player.lastZ = teleport.getZ();
player.lastYaw = teleport.getYaw();
player.lastPitch = teleport.getPitch();
player.lastX = posX;
player.lastY = posY;
player.lastZ = posZ;
player.lastYaw = yaw;
player.lastPitch = pitch;
player.pollData();
}
@@ -67,24 +70,24 @@ public class PacketServerTeleport extends PacketListenerAbstract {
relativeZ = teleport.isRelativeFlag(RelativeFlag.Z);
if (relativeX) {
pos = pos.add(new Vector3d(player.x, 0, 0));
posX += player.x;
teleport.setRelative(RelativeFlag.X, false);
}
if (relativeY) {
pos = pos.add(new Vector3d(0, player.y, 0));
posY += player.y;
teleport.setRelative(RelativeFlag.Y, false);
}
if (relativeZ) {
pos = pos.add(new Vector3d(0, 0, player.z));
posZ += player.z;
teleport.setRelative(RelativeFlag.Z, false);
}
if (relativeX || relativeY || relativeZ) {
teleport.setX(pos.getX());
teleport.setY(pos.getY());
teleport.setZ(pos.getZ());
teleport.setX(posX);
teleport.setY(posY);
teleport.setZ(posZ);
event.markForReEncode(true);
}
@@ -124,10 +127,13 @@ public class PacketServerTeleport extends PacketListenerAbstract {
// For some reason teleports on 1.7 servers are offset by 1.62?
if (PacketEvents.getAPI().getServerManager().getVersion().isOlderThan(ServerVersion.V_1_8))
pos = pos.withY(pos.getY() - 1.62);
posY -= 1.62;
Location target = new Location(null, pos.getX(), pos.getY(), pos.getZ());
player.getSetbackTeleportUtil().addSentTeleport(target, teleport.getDeltaMovement(), lastTransactionSent, teleport.getRelativeFlags(), true, teleport.getTeleportId());
final Vector3d deltaMovement = teleport.getDeltaMovement();
player.getSetbackTeleportUtil().addSentTeleport(posX, posY, posZ,
deltaMovement.getX(), deltaMovement.getY(), deltaMovement.getZ(),
lastTransactionSent, teleport.getRelativeFlags(), true, teleport.getTeleportId()
);
}
if (event.getPacketType() == PacketType.Play.Server.PLAYER_ROTATION) {
@@ -157,9 +163,12 @@ public class PacketServerTeleport extends PacketListenerAbstract {
player.sendTransaction();
event.getTasksAfterSend().add(player::sendTransaction);
player.vehicleData.vehicleTeleports.add(new Pair<>(
Vector3d position = new WrapperPlayServerVehicleMove(event).getPosition();
player.vehicleData.vehicleTeleports.add(new VehicleData.VehicleTeleport(
player.lastTransactionSent.get(),
new WrapperPlayServerVehicleMove(event).getPosition()
position.getX(),
position.getY(),
position.getZ()
));
}
}
@@ -77,7 +77,11 @@ public class BasePacketWorldReader extends PacketListenerAbstract {
if (player == null) return;
WrapperPlayServerAcknowledgePlayerDigging ack = new WrapperPlayServerAcknowledgePlayerDigging(event);
player.compensatedWorld.handleBlockBreakAck(ack.getBlockPosition(), ack.getBlockId(), ack.getAction(), ack.isSuccessful());
Vector3i blockPosition = ack.getBlockPosition();
final int x = blockPosition.getX();
final int y = blockPosition.getY();
final int z = blockPosition.getZ();
player.compensatedWorld.handleBlockBreakAck(x, y, z, ack.getBlockId(), ack.getAction(), ack.isSuccessful());
}
if (event.getPacketType() == PacketType.Play.Server.CHANGE_GAME_STATE) {
@@ -121,7 +125,7 @@ public class BasePacketWorldReader extends PacketListenerAbstract {
if (teleports.getFlags().getMask() != 0) {
continue; // Worse that will happen is people will get an extra setback...
}
shouldPostTrans = shouldPostTrans || (Math.abs(teleports.getLocation().getX() - chunkCenterX) < 16 && Math.abs(teleports.getLocation().getZ() - chunkCenterZ) < 16);
shouldPostTrans = shouldPostTrans || (Math.abs(teleports.getLocationX() - chunkCenterX) < 16 && Math.abs(teleports.getLocationZ() - chunkCenterZ) < 16);
}
if (shouldPostTrans) {
@@ -116,6 +116,7 @@ public class CheckManager {
.put(BadPacketsU.class, new BadPacketsU(player))
.put(BadPacketsV.class, new BadPacketsV(player))
.put(BadPacketsY.class, new BadPacketsY(player))
.put(BadPacketsZ.class, new BadPacketsZ(player))
.put(MultiActionsA.class, new MultiActionsA(player))
.put(MultiActionsC.class, new MultiActionsC(player))
.put(MultiActionsD.class, new MultiActionsD(player))
@@ -124,6 +125,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
}
@@ -18,9 +18,9 @@ import ac.grim.grimac.utils.data.SetBackData;
import ac.grim.grimac.utils.data.TeleportAcceptData;
import ac.grim.grimac.utils.data.TeleportData;
import ac.grim.grimac.utils.data.VectorData;
import ac.grim.grimac.utils.data.VehicleData;
import ac.grim.grimac.utils.data.VelocityData;
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.math.VectorUtils;
import ac.grim.grimac.utils.nmsutil.Collisions;
@@ -74,7 +74,9 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
@Override
public void onPredictionComplete(final PredictionComplete predictionComplete) {
// Grab friction now when we know player on ground and other variables
Vector3dm afterTickFriction = player.clientVelocity.clone();
final double afterTickFrictionX = player.clientVelocity.getX();
final double afterTickFrictionY = player.clientVelocity.getY();
final double afterTickFrictionZ = player.clientVelocity.getZ();
// We must first check if the player has accepted their setback
// If the setback isn't complete, then this position is illegitimate
@@ -82,12 +84,12 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
// The player needs to now wait for their vehicle to go into the right place before getting back in
if (cheatVehicleInterpolationDelay > 0) cheatVehicleInterpolationDelay = 10;
// Teleport, let velocity be reset
lastKnownGoodPosition = new SetbackPosWithVector(new Vector3d(player.x, player.y, player.z), afterTickFriction);
lastKnownGoodPosition = new SetbackPosWithVector(player.x, player.y, player.z, afterTickFrictionX, afterTickFrictionY, afterTickFrictionZ);
} else if (requiredSetBack == null || requiredSetBack.isComplete()) {
cheatVehicleInterpolationDelay--;
// No simulation... we can do that later. We just need to know the valid position.
// As we didn't setback here, the new position is known to be safe!
lastKnownGoodPosition = new SetbackPosWithVector(new Vector3d(player.x, player.y, player.z), afterTickFriction);
lastKnownGoodPosition = new SetbackPosWithVector(player.x, player.y, player.z, afterTickFrictionX, afterTickFrictionY, afterTickFrictionZ);
}
if (requiredSetBack != null) requiredSetBack.tick();
@@ -123,27 +125,6 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
return player.platformPlayer != null && player.noSetbackPermission;
}
private void simulateFriction(Vector3dm vector) {
// We must always do this before simulating positions, as this is the last actual (safe) movement
// We must not do this for knockback or explosions, as they are at the start of the tick
if (player.wasTouchingWater) {
PredictionEngineWater.staticVectorEndOfTick(player, vector, 0.8F, player.gravity, true);
} else if (player.wasTouchingLava) {
vector.multiply(0.5D);
if (player.hasGravity)
vector.add(new Vector3dm(0.0D, -player.gravity / 4.0D, 0.0D));
} else if (player.isGliding) {
PredictionEngineElytra.getElytraMovement(player, vector, ReachUtils.getLook(player, player.yaw, player.pitch)).multiply(player.stuckSpeedMultiplier).multiply(new Vector3dm(0.99F, 0.98F, 0.99F));
vector.setY(vector.getY() - 0.05); // Make the player fall a bit
} else { // Gliding doesn't have friction, we handle it differently
PredictionEngineNormal.staticVectorEndOfTick(player, vector); // Lava and normal movement
vector.multiply(player.stuckSpeedMultiplier); // Prevent abusing setbacks to move out of blocks like webs
}
// stop 1.8 players from stepping onto 1.25 high blocks, because why not?
new PredictionEngine().applyMovementThreshold(player, new HashSet<>(Collections.singletonList(new VectorData(vector, VectorData.VectorType.BestVelPicked))));
}
private void blockMovementsUntilResync(boolean simulateNextTickPosition, boolean isResync) {
if (requiredSetBack == null) return; // Hasn't spawned
if (player.platformPlayer != null && player.noSetbackPermission)
@@ -157,64 +138,115 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
lastWorldResync = System.currentTimeMillis();
}
Vector3dm clientVel = lastKnownGoodPosition.vector.clone();
double clientVelX = lastKnownGoodPosition.getVectorX();
double clientVelY = lastKnownGoodPosition.getVectorY();
double clientVelZ = lastKnownGoodPosition.getVectorZ();
Pair<VelocityData, Vector3dm> futureKb = player.checkManager.getKnockbackHandler().getFutureKnockback();
VelocityData futureExplosion = player.checkManager.getExplosionHandler().getFutureExplosion();
// Velocity sets
if (futureKb.first() != null) {
clientVel = futureKb.second();
clientVelX = futureKb.second().getX();
clientVelY = futureKb.second().getY();
clientVelZ = futureKb.second().getZ();
}
// Explosion adds
if (futureExplosion != null && (futureKb.first() == null || futureKb.first().transaction < futureExplosion.transaction)) {
clientVel.add(futureExplosion.vector);
clientVelX += futureExplosion.vector.getX();
clientVelY += futureExplosion.vector.getY();
clientVelZ += futureExplosion.vector.getZ();
}
Vector3d position = lastKnownGoodPosition.pos;
double x = lastKnownGoodPosition.posX;
double y = lastKnownGoodPosition.posY;
double z = lastKnownGoodPosition.posZ;
SimpleCollisionBox oldBB = player.boundingBox;
player.boundingBox = GetBoundingBox.getPlayerBoundingBox(player, position.getX(), position.getY(), position.getZ());
player.boundingBox = GetBoundingBox.getPlayerBoundingBox(player, x, y, z);
// Mini prediction engine - simulate collisions
if (simulateNextTickPosition) {
Vector3dm collide = Collisions.collide(player, clientVel.getX(), clientVel.getY(), clientVel.getZ());
Vector3dm collide = Collisions.collide(player, clientVelX, clientVelY, clientVelZ);
position = position.withX(position.getX() + collide.getX());
position = position.withY(position.getY() + collide.getY());
x += collide.getX();
y += collide.getY();
// TODO: Is this even needed? Can't reproduce any phasing on vanilla 1.8 when being setback.
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_9)) {
// 1.8 players need the collision epsilon to not phase into blocks when being setback
// Due to simulation, this will not allow a flight bypass by sending a billion invalid movements
position = position.withY(position.getY() + SimpleCollisionBox.COLLISION_EPSILON);
y += SimpleCollisionBox.COLLISION_EPSILON;
}
position = position.withZ(position.getZ() + collide.getZ());
z += collide.getZ();
if (clientVel.getX() != collide.getX()) clientVel.setX(0);
if (clientVel.getY() != collide.getY()) clientVel.setY(0);
if (clientVel.getZ() != collide.getZ()) clientVel.setZ(0);
if (clientVelX != collide.getX()) clientVelX = 0;
if (clientVelY != collide.getY()) clientVelY = 0;
if (clientVelZ != collide.getZ()) clientVelZ = 0;
simulateFriction(clientVel);
// BEGIN SIMULATING FRICTION
// We must always do this before simulating positions, as this is the last actual (safe) movement
// We must not do this for knockback or explosions, as they are at the start of the tick
double[] vector = {clientVelX, clientVelY, clientVelZ};
if (player.wasTouchingWater) {
PredictionEngineWater.staticVectorEndOfTick(player, vector, 0.8F, player.gravity, true);
} else if (player.wasTouchingLava) {
vector[0] *= 0.5D;
vector[1] *= 0.5D;
vector[2] *= 0.5D;
if (player.hasGravity)
vector[1] += -player.gravity / 4.0D;
} else if (player.isGliding) {
Vector3dm getLook = ReachUtils.getLook(player, player.yaw, player.pitch);
PredictionEngineElytra.applyElytraMovementInPlace(player, vector, getLook.getX(), getLook.getY(), getLook.getZ());
vector[0] *= player.stuckSpeedMultiplier.getX() * 0.99F;
vector[1] *= player.stuckSpeedMultiplier.getY() * 0.98F;
vector[2] *= player.stuckSpeedMultiplier.getZ() * 0.99F;
vector[1] -= 0.05; // Make the player fall a bit
} else { // Gliding doesn't have friction, we handle it differently
// Lava and normal movement
vector[0] *= player.friction;
vector[1] = PredictionEngineNormal.staticVectorEndOfTickY(player, vector[1]);
vector[2] *= player.friction;
// Prevent abusing setbacks to move out of blocks like webs
vector[0] *= player.stuckSpeedMultiplier.getX();
vector[1] *= player.stuckSpeedMultiplier.getY();
vector[2] *= player.stuckSpeedMultiplier.getZ();
}
// stop 1.8 players from stepping onto 1.25 high blocks, because why not?
new PredictionEngine().applyMovementThreshold(player, new HashSet<>(Collections.singletonList(new VectorData(vector[0], vector[1], vector[2], VectorData.VectorType.BestVelPicked))));
// END SIMULATING FRICTION
clientVelX = vector[0];
clientVelY = vector[1];
clientVelZ = vector[2];
}
player.boundingBox = oldBB; // reset back to the new bounding box
boolean sendVelocity = true;
if (!hasAcceptedSpawnTeleport || player.isFlying)
clientVel = null; // if the player is flying or hasn't spawned... don't force kb
sendVelocity = false; // if the player is flying or hasn't spawned... don't force kb
// Something weird has occurred in the player's movement, block offsets until we resync
if (isResync) {
blockOffsets = true;
}
SetBackData data = new SetBackData(new TeleportData(position, new Vector3d(), new RelativeFlag(0b11000), player.lastTransactionSent.get(), 0), player.yaw, player.pitch, clientVel, player.inVehicle(), false);
SetBackData data = new SetBackData(new TeleportData(x, y, z, 0, 0, 0, new RelativeFlag(0b11000), player.lastTransactionSent.get(), 0), player.yaw, player.pitch, clientVelX, clientVelY, clientVelZ, sendVelocity, player.inVehicle(), false);
sendSetback(data);
}
private void sendSetback(SetBackData data) {
isSendingSetback = true;
Vector3d position = data.getTeleportData().getLocation();
double positionX = data.getTeleportData().getLocationX();
double positionY = data.getTeleportData().getLocationY();
double positionZ = data.getTeleportData().getLocationZ();
try {
// Player is in a vehicle
@@ -230,7 +262,7 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
// Stop the player from being able to teleport vehicles and simply re-enter them to continue,
// therefore, teleport the entity
player.user.sendPacket(new WrapperPlayServerEntityTeleport(vehicleId, new Vector3d(position.getX(), position.getY(), position.getZ()), player.yaw % 360, 0, false));
player.user.sendPacket(new WrapperPlayServerEntityTeleport(vehicleId, new Vector3d(positionX, positionY, positionZ), player.yaw % 360, 0, false));
player.getSetbackTeleportUtil().cheatVehicleInterpolationDelay = Integer.MAX_VALUE; // Set to max until player accepts the new position
// Make sure bukkit also knows the player got teleported out of their vehicle, can't do this async
@@ -245,7 +277,7 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
}
}
double y = position.getY();
double y = positionY;
if (PacketEvents.getAPI().getServerManager().getVersion().isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
y += 1.62; // 1.7 teleport offset if grim ever supports 1.7 again
}
@@ -260,15 +292,15 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
data.getTeleportData().setTransaction(player.lastTransactionSent.get());
// Use provided transaction ID to make sure it can never desync, although there's no reason to do this
addSentTeleport(new Location(null, position.getX(), y, position.getZ(), player.yaw % 360, player.pitch % 360), new Vector3d(), data.getTeleportData().getTransaction(), new RelativeFlag(0b11000), false, teleportId);
addSentTeleport(positionX, y, positionZ, 0, 0, 0, data.getTeleportData().getTransaction(), new RelativeFlag(0b11000), false, teleportId);
// This must be done after setting the sent teleport, otherwise we lose velocity data
requiredSetBack = data;
// Send after tracking to fix race condition
PacketEvents.getAPI().getProtocolManager().sendPacketSilently(player.user.getChannel(), new WrapperPlayServerPlayerPositionAndLook(position.getX(), position.getY(), position.getZ(), 0, 0, data.getTeleportData().getFlags().getMask(), teleportId, false));
PacketEvents.getAPI().getProtocolManager().sendPacketSilently(player.user.getChannel(), new WrapperPlayServerPlayerPositionAndLook(positionX, positionY, positionZ, 0, 0, data.getTeleportData().getFlags().getMask(), teleportId, false));
player.sendTransaction();
if (data.getVelocity() != null && data.getVelocity().lengthSquared() > 0) {
player.user.sendPacket(new WrapperPlayServerEntityVelocity(player.entityID, new Vector3d(data.getVelocity().getX(), data.getVelocity().getY(), data.getVelocity().getZ())));
if (data.isHasVelocity() && (data.getVelocityX() != 0 || data.getVelocityY() != 0 || data.getVelocityZ() != 0)) {
player.user.sendPacket(new WrapperPlayServerEntityVelocity(player.entityID, new Vector3d(data.getVelocityX(), data.getVelocityY(), data.getVelocityZ())));
}
} finally {
isSendingSetback = false;
@@ -288,12 +320,12 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
TeleportData teleportPos;
while ((teleportPos = pendingTeleports.peek()) != null) {
double trueTeleportX = (teleportPos.isRelativeX() ? player.x : 0) + teleportPos.getLocation().getX();
double trueTeleportY = (teleportPos.isRelativeY() ? player.y : 0) + teleportPos.getLocation().getY();
double trueTeleportZ = (teleportPos.isRelativeZ() ? player.z : 0) + teleportPos.getLocation().getZ();
double trueTeleportX = (teleportPos.isRelativeX() ? player.x : 0) + teleportPos.getLocationX();
double trueTeleportY = (teleportPos.isRelativeY() ? player.y : 0) + teleportPos.getLocationY();
double trueTeleportZ = (teleportPos.isRelativeZ() ? player.z : 0) + teleportPos.getLocationZ();
// There seems to be a version difference in teleports past 30 million... just clamp the vector
Vector3d clamped = VectorUtils.clampVector(new Vector3d(trueTeleportX, trueTeleportY, trueTeleportZ));
Vector3d clamped = VectorUtils.clampVector(trueTeleportX, trueTeleportY, trueTeleportZ);
double threshold = teleportPos.isRelativePos() ? player.getMovementThreshold() : 0;
boolean closeEnoughY = Math.abs(clamped.getY() - y) <= 1e-7 + threshold; // 1.7 rounding
@@ -339,18 +371,17 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
int lastTransaction = player.lastTransactionReceived.get();
while (true) {
Pair<Integer, Vector3d> teleportPos = player.vehicleData.vehicleTeleports.peek();
VehicleData.VehicleTeleport teleportPos = player.vehicleData.vehicleTeleports.peek();
if (teleportPos == null) break;
if (lastTransaction < teleportPos.first()) {
if (lastTransaction < teleportPos.teleportId()) {
break;
}
Vector3d position = teleportPos.second();
if (position.getX() == x && position.getY() == y && position.getZ() == z) {
if (teleportPos.x() == x && teleportPos.y() == y && teleportPos.z() == z) {
player.vehicleData.vehicleTeleports.poll();
return true;
} else if (lastTransaction > teleportPos.first() + 1) {
} else if (lastTransaction > teleportPos.teleportId() + 1) {
player.vehicleData.vehicleTeleports.poll();
// Vehicles have terrible netcode so just ignore it if the teleport wasn't from us setting the player back
@@ -396,36 +427,37 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
!player.getSetbackTeleportUtil().hasAcceptedSpawnTeleport);
}
public void addSentTeleport(Location position, Vector3d velocity, int transaction, RelativeFlag flags, boolean plugin, int teleportId) {
TeleportData data = new TeleportData(new Vector3d(position.getX(), position.getY(), position.getZ()), velocity, flags, transaction, teleportId);
public void addSentTeleport(double x, double y, double z, double velocityX, double velocityY, double velocityZ, int transaction, RelativeFlag flags, boolean plugin, int teleportId) {
TeleportData data = new TeleportData(x, y, z, velocityX, velocityY, velocityZ, flags, transaction, teleportId);
pendingTeleports.add(data);
Vector3d safePosition = new Vector3d(position.getX(), position.getY(), position.getZ());
// We must convert relative teleports to avoid them becoming client controlled in the case of setback
if (flags.has(RelativeFlag.X)) {
safePosition = safePosition.withX(safePosition.getX() + lastKnownGoodPosition.pos.getX());
x += lastKnownGoodPosition.posX;
}
if (flags.has(RelativeFlag.Y)) {
safePosition = safePosition.withY(safePosition.getY() + lastKnownGoodPosition.pos.getY());
y += lastKnownGoodPosition.posY;
}
if (flags.has(RelativeFlag.Z)) {
safePosition = safePosition.withZ(safePosition.getZ() + lastKnownGoodPosition.pos.getZ());
z += lastKnownGoodPosition.posZ;
}
data = new TeleportData(safePosition, velocity, new RelativeFlag(0b11000), transaction, teleportId);
requiredSetBack = new SetBackData(data, player.yaw, player.pitch, null, false, plugin);
data = new TeleportData(x, y, z, velocityX, velocityY, velocityZ, new RelativeFlag(0b11000), transaction, teleportId);
requiredSetBack = new SetBackData(data, player.yaw, player.pitch, false, plugin);
this.lastKnownGoodPosition = new SetbackPosWithVector(safePosition, new Vector3dm());
this.lastKnownGoodPosition = new SetbackPosWithVector(x, y, z, 0, 0, 0);
}
@AllArgsConstructor
@Getter
@Setter
public static class SetbackPosWithVector {
private final Vector3d pos;
private Vector3dm vector;
private final double posX;
private final double posY;
private final double posZ;
private final double vectorY;
private double vectorX, vectorZ;
}
}
@@ -6,10 +6,12 @@ import ac.grim.grimac.checks.type.PostPredictionCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.predictionengine.UncertaintyHandler;
import ac.grim.grimac.predictionengine.predictions.PredictionEngine;
import ac.grim.grimac.utils.anticheat.MessageUtil;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import ac.grim.grimac.utils.data.VectorData;
import ac.grim.grimac.utils.lists.EvictingQueue;
import ac.grim.grimac.utils.math.GrimMath;
import ac.grim.grimac.utils.math.Vec2;
import ac.grim.grimac.utils.math.Vector3dm;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
@@ -54,7 +56,12 @@ public final class SuperDebug extends Check implements PostPredictionCheck {
for (Iterator<Object2IntMap.Entry<StringBuilder>> it = continuedDebug.object2IntEntrySet().iterator(); it.hasNext(); ) {
Map.Entry<StringBuilder, Integer> debug = it.next();
appendDebug(debug.getKey(), player.predictedVelocity, player.actualMovement, location, player.startTickClientVel, player.baseTickAddition, player.baseTickWaterPushing);
appendDebug(debug.getKey(), player.predictedVelocity,
player.actualMovement.getX(), player.actualMovement.getY(), player.actualMovement.getZ(),
location,
player.startTickClientVel.getX(), player.startTickClientVel.getY(), player.startTickClientVel.getZ(),
player.baseTickAddition.getX(), player.baseTickAddition.getY(), player.baseTickAddition.getZ(),
player.baseTickWaterPushing.getX(), player.baseTickWaterPushing.getY(), player.baseTickWaterPushing.getZ());
debug.setValue(debug.getValue() - 1);
if (debug.getValue() <= 0) it.remove();
}
@@ -90,7 +97,14 @@ public final class SuperDebug extends Check implements PostPredictionCheck {
Vector3dm startTickVel = startTickClientVel.get(i);
Vector3dm addition = baseTickAddition.get(i);
Vector3dm water = baseTickWater.get(i);
appendDebug(sb, predict, actual, loc, startTickVel, addition, water);
appendDebug(sb,
predict,
actual.getX(), actual.getY(), actual.getZ(),
loc,
startTickVel.getX(), startTickVel.getY(), startTickVel.getZ(),
addition.getX(), addition.getY(), addition.getZ(),
water.getX(), water.getY(), water.getZ()
);
}
UncertaintyHandler uncertaintyHandler = player.uncertaintyHandler;
@@ -194,7 +208,13 @@ public final class SuperDebug extends Check implements PostPredictionCheck {
continuedDebug.put(sb, 40);
}
private void appendDebug(StringBuilder sb, VectorData predict, Vector3dm actual, Location location, Vector3dm startTick, Vector3dm addition, Vector3dm water) {
private void appendDebug(final StringBuilder sb, final VectorData predict,
final double actualX, final double actualY, final double actualZ,
final Location location,
final double startTickX, final double startTickY, final double startTickZ,
final double additionX, final double additionY, final double additionZ,
final double waterX, final double waterY, final double waterZ) {
if (predict.isZeroPointZeroThree()) {
sb.append("Movement threshold/tick skipping\n");
}
@@ -228,36 +248,45 @@ public final class SuperDebug extends Check implements PostPredictionCheck {
}
// Apply 0.003/0.005 to make numbers more accurate
Set<VectorData> set = new HashSet<>(Collections.singletonList(new VectorData(startTick.clone(), VectorData.VectorType.BestVelPicked)));
Set<VectorData> set = new HashSet<>(Collections.singletonList(new VectorData(startTickX, startTickY, startTickZ, VectorData.VectorType.BestVelPicked)));
new PredictionEngine().applyMovementThreshold(player, set);
Vector3dm trueStartVel = ((VectorData) set.toArray()[0]).vector;
VectorData tempVectorData = ((VectorData) set.toArray()[0]);
double trueStartVelX = tempVectorData.vectorX;
double trueStartVelY = tempVectorData.vectorY;
double trueStartVelZ = tempVectorData.vectorZ;
Vector3dm clientMovement = getPlayerMathMovement(player, actual.clone().subtract(trueStartVel), location.xRot);
Vector3dm simulatedMovement = getPlayerMathMovement(player, predict.vector.clone().subtract(trueStartVel), location.xRot);
Vector3dm offset = actual.clone().subtract(predict.vector);
trueStartVel.add(addition);
trueStartVel.add(water);
final double offsetX = actualX - trueStartVelX;
final double offsetY = actualY - trueStartVelY;
final double offsetZ = actualZ - trueStartVelZ;
double offsetLength = Math.sqrt(offsetX*offsetX + offsetY*offsetY + offsetZ*offsetZ);
Vec2 clientMovement = getPlayerMathMovement(player, offsetX, offsetZ, location.xRot);
Vec2 simulatedMovement = getPlayerMathMovement(player, predict.vectorX - trueStartVelX, predict.vectorZ - trueStartVelZ, location.xRot);
trueStartVelX += additionX + waterX;
trueStartVelY += additionY + waterY;
trueStartVelZ += additionZ + waterZ;
sb.append("Simulated: ");
sb.append(predict.vector.toString());
sb.append(MessageUtil.toUnlabeledString(trueStartVelX, trueStartVelY, trueStartVelZ));
sb.append("\nActually: ");
sb.append(actual);
sb.append(MessageUtil.toUnlabeledString(actualX, actualY, actualZ));
sb.append("\nOffset Vector: ");
sb.append(offset);
sb.append(MessageUtil.toUnlabeledString(offsetX, offsetY, offsetZ));
sb.append("\nOffset: ");
sb.append(offset.length());
sb.append(offsetLength);
sb.append("\nLocation: ");
sb.append(location);
sb.append("\nInitial velocity: ");
sb.append(startTick);
sb.append(MessageUtil.toUnlabeledString(startTickX, startTickY, startTickZ));
if (addition.lengthSquared() > 0) {
if (additionX != 0 || additionY != 0 || additionZ != 0) {
sb.append("\nInitial vel addition: ");
sb.append(addition);
sb.append(MessageUtil.toUnlabeledString(additionX, additionY, additionZ));
}
if (water.lengthSquared() > 0) {
if (waterX != 0 || waterY != 0 || waterZ != 0) {
sb.append("\nWater vel addition: ");
sb.append(water);
sb.append(MessageUtil.toUnlabeledString(waterX, waterY, waterZ));
}
sb.append("\nClient input: ");
@@ -273,14 +302,14 @@ public final class SuperDebug extends Check implements PostPredictionCheck {
sb.append("\n\n");
}
private Vector3dm getPlayerMathMovement(GrimPlayer player, Vector3dm wantedMovement, float f2) {
private Vec2 getPlayerMathMovement(GrimPlayer player, double x, double z, float f2) {
float f3 = player.trigHandler.sin(f2 * 0.017453292f);
float f4 = player.trigHandler.cos(f2 * 0.017453292f);
float bestTheoreticalX = (float) (f3 * wantedMovement.getZ() + f4 * wantedMovement.getX()) / (f3 * f3 + f4 * f4);
float bestTheoreticalZ = (float) (-f3 * wantedMovement.getX() + f4 * wantedMovement.getZ()) / (f3 * f3 + f4 * f4);
float bestTheoreticalX = (float) (f3 * z + f4 * x) / (f3 * f3 + f4 * f4);
float bestTheoreticalZ = (float) (-f3 * x + f4 * z) / (f3 * f3 + f4 * f4);
return new Vector3dm(bestTheoreticalX, 0, bestTheoreticalZ);
return new Vec2(bestTheoreticalX, bestTheoreticalZ);
}
private record Location(double x, double y, double z, float xRot, float yRot, String world) {
@@ -7,7 +7,6 @@ import ac.grim.grimac.platform.api.world.PlatformWorld;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.util.Vector3d;
import com.github.retrooper.packetevents.util.Vector3i;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerAcknowledgeBlockChanges;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerBlockChange;
@@ -116,7 +115,7 @@ public class DefaultResyncHandler implements ResyncHandler {
GrimAPI.INSTANCE.getScheduler().getRegionScheduler().execute(GrimAPI.INSTANCE.getGrimPlugin(), world, chunkX, chunkZ, () -> {
if (!player.platformPlayer.isOnline() || !player.getSetbackTeleportUtil().hasAcceptedSpawnTeleport)
return;
if (player.platformPlayer.getPosition().distance(new Vector3d(x, y, z)) >= 64)
if (player.platformPlayer.distanceSquared(x, y, z) >= 64 * 64)
return;
if (!world.isChunkLoaded(chunkX, chunkZ)) return; // Don't load chunks sync
@@ -25,4 +25,6 @@ public interface GrimEntity extends GrimIdentity {
PlatformWorld getWorld();
Location getLocation();
double distanceSquared(double x, double y, double z);
}
@@ -364,12 +364,15 @@ public class GrimPlayer implements GrimUser {
// It's very difficult to test precedence so if there's issues with this bouncy implementation let me know
for (VectorData data : new HashSet<>(possibleMovements)) {
for (BlockFace direction : uncertaintyHandler.slimePistonBounces) {
final double x = data.vectorX;
final double y = data.vectorY;
final double z = data.vectorZ;
if (direction.getModX() != 0) {
possibleMovements.add(data.returnNewModified(data.vector.clone().setX(direction.getModX()), VectorData.VectorType.SlimePistonBounce));
possibleMovements.add(data.returnNewModified(direction.getModX(), y, z, VectorData.VectorType.SlimePistonBounce));
} else if (direction.getModY() != 0) {
possibleMovements.add(data.returnNewModified(data.vector.clone().setY(direction.getModY()), VectorData.VectorType.SlimePistonBounce));
possibleMovements.add(data.returnNewModified(x, direction.getModY(), z, VectorData.VectorType.SlimePistonBounce));
} else if (direction.getModZ() != 0) {
possibleMovements.add(data.returnNewModified(data.vector.clone().setZ(direction.getModZ()), VectorData.VectorType.SlimePistonBounce));
possibleMovements.add(data.returnNewModified(x, y, direction.getModZ(), VectorData.VectorType.SlimePistonBounce));
}
}
}
@@ -796,7 +799,10 @@ public class GrimPlayer implements GrimUser {
}
public boolean isInWaterOrRain() {
return compensatedWorld.isRaining || Collisions.hasMaterial(this, boundingBox.copy().expand(0.1f), (block) -> Materials.isWater(CompensatedWorld.blockVersion, block.first()));
return compensatedWorld.isRaining
|| Collisions.hasMaterial(this, boundingBox.copy().expand(0.1f),
(block, x, y, z) -> Materials.isWater(CompensatedWorld.blockVersion, block)
);
}
@Contract(pure = true)
@@ -925,12 +931,12 @@ public class GrimPlayer implements GrimUser {
if (platformPlayer != null) platformPlayer.sendMessage(message);
}
public void resyncPosition(Vector3i pos) {
this.resyncHandler.resync(pos.getX(), pos.getY(), pos.getZ(), pos.getX(), pos.getY(), pos.getZ());
public void resyncPosition(final int x, final int y, final int z) {
this.resyncHandler.resync(x, y, z, x, y, z);
}
public void resyncPosition(Vector3i pos, int sequenceID) {
this.resyncHandler.resyncPosition(pos.x, pos.y, pos.z, sequenceID);
public void resyncPosition(final int x, final int y, final int z, final int sequenceID) {
this.resyncHandler.resyncPosition(x, y, z, sequenceID);
}
public void resyncPositions(SimpleCollisionBox box) {
@@ -942,14 +948,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() {
@@ -18,6 +18,7 @@ import ac.grim.grimac.predictionengine.predictions.rideable.PredictionEngineBoat
import ac.grim.grimac.utils.anticheat.update.PositionUpdate;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
import ac.grim.grimac.utils.data.SetBackData;
import ac.grim.grimac.utils.data.VectorData;
import ac.grim.grimac.utils.data.packetentity.PacketEntity;
import ac.grim.grimac.utils.data.packetentity.PacketEntityCamel;
@@ -196,7 +197,6 @@ public class MovementCheckRunner extends Check implements PositionCheck {
player.vehicleData.wasVehicleSwitch = false;
if (riding != null) {
Vector3dm pos = new Vector3dm(player.x, player.y, player.z);
SimpleCollisionBox interTruePositions = riding.getPossibleCollisionBoxes();
// We shrink the expanded bounding box to what the packet positions can be, for a smaller box
@@ -206,7 +206,7 @@ public class MovementCheckRunner extends Check implements PositionCheck {
interTruePositions.expand(-width, 0, -width);
interTruePositions.expandMax(0, -height, 0);
Vector3dm cutTo = VectorUtils.cutBoxToVector(pos, interTruePositions);
Vector3dm cutTo = VectorUtils.cutBoxToVector(player.x, player.y, player.z, interTruePositions);
// Now we need to simulate a tick starting at the most optimal position
// The start position is never sent, so we assume the most optimal start position
@@ -231,9 +231,7 @@ public class MovementCheckRunner extends Check implements PositionCheck {
handleTeleport(update);
if (player.isClimbing) {
Vector3dm ladder = player.clientVelocity.clone().setY(0.2);
PredictionEngineNormal.staticVectorEndOfTick(player, ladder);
player.lastWasClimbing = ladder.getY();
player.lastWasClimbing = PredictionEngineNormal.staticVectorEndOfTickY(player, 0.2);
}
return;
}
@@ -363,8 +361,7 @@ public class MovementCheckRunner extends Check implements PositionCheck {
}
SimpleCollisionBox steppingOnBB = GetBoundingBox.getCollisionBoxForPlayer(player, player.x, player.y, player.z).expand(player.getMovementThreshold()).offset(0, -1, 0);
Collisions.hasMaterial(player, steppingOnBB, (pair) -> {
WrappedBlockState data = pair.first();
Collisions.hasMaterial(player, steppingOnBB, (data) -> {
if (data.getType() == StateTypes.SLIME_BLOCK && player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_8)) {
player.uncertaintyHandler.isSteppingOnSlime = true;
player.uncertaintyHandler.isSteppingOnBouncyBlock = true;
@@ -410,8 +407,8 @@ public class MovementCheckRunner extends Check implements PositionCheck {
player.uncertaintyHandler.isNearGlitchyBlock = player.getClientVersion().isOlderThan(ClientVersion.V_1_9)
&& Collisions.hasMaterial(player, expandedBB.copy().expand(0.2),
checkData -> BlockTags.ANVIL.contains(checkData.first().getType())
|| checkData.first().getType() == StateTypes.CHEST || checkData.first().getType() == StateTypes.TRAPPED_CHEST);
checkData -> BlockTags.ANVIL.contains(checkData.getType())
|| checkData.getType() == StateTypes.CHEST || checkData.getType() == StateTypes.TRAPPED_CHEST);
player.uncertaintyHandler.isOrWasNearGlitchyBlock = isGlitchy || player.uncertaintyHandler.isNearGlitchyBlock;
player.uncertaintyHandler.checkForHardCollision();
@@ -453,7 +450,9 @@ public class MovementCheckRunner extends Check implements PositionCheck {
player.clientVelocity = player.actualMovement.clone();
player.gravity = 0;
player.friction = 0.91f;
PredictionEngineNormal.staticVectorEndOfTick(player, player.clientVelocity);
player.clientVelocity.setX(player.clientVelocity.getX() * player.friction);
player.clientVelocity.setY(PredictionEngineNormal.staticVectorEndOfTickY(player, player.clientVelocity.getY()));
player.clientVelocity.setZ(player.clientVelocity.getZ() * player.friction);
} else if (riding == null) {
wasChecked = true;
@@ -522,7 +521,7 @@ public class MovementCheckRunner extends Check implements PositionCheck {
} // If it isn't any of these cases, the player is on a mob they can't control and therefore is exempt
// No, don't comment about the sqrt call. It doesn't matter unless you run sqrt thousands of times a second.
double offset = player.predictedVelocity.vector.distance(player.actualMovement);
double offset = player.actualMovement.distance(player.predictedVelocity.vectorX, player.predictedVelocity.vectorY, player.predictedVelocity.vectorZ);
offset = player.uncertaintyHandler.reduceOffset(offset);
if (player.packetStateData.tryingToRiptide != clientClaimsRiptide) {
@@ -535,19 +534,19 @@ public class MovementCheckRunner extends Check implements PositionCheck {
//
// Checking for oldClientVel being too high fixes BleachHack vertical scaffold
if (player.getSetbackTeleportUtil().getRequiredSetBack() != null && player.getSetbackTeleportUtil().getRequiredSetBack().getTicksComplete() == 1) {
Vector3dm setbackVel = player.getSetbackTeleportUtil().getRequiredSetBack().getVelocity();
SetBackData setback = player.getSetbackTeleportUtil().getRequiredSetBack();
// A player must have velocity going INTO the ground to be able to jump
// Otherwise they could ignore upwards velocity that isn't useful into more useful upwards velocity (towering)
// So if they are supposed to be going upwards, or are supposed to be off the ground, resync
if (player.predictedVelocity.isJump()
&& !player.wasTouchingLava && !player.wasTouchingWater
&& ((setbackVel != null && setbackVel.getY() >= 0) || !Collisions.slowCouldPointThreeHitGround(player, player.lastX, player.lastY, player.lastZ))) {
&& ((setback.isHasVelocity() && setback.getVelocityY() >= 0) || !Collisions.slowCouldPointThreeHitGround(player, player.lastX, player.lastY, player.lastZ))) {
player.getSetbackTeleportUtil().executeForceResync();
}
boolean lavaBugFix = player.wasTouchingLava && player.predictedVelocity.isJump() &&
player.predictedVelocity.vector.getY() < 0.06 && player.predictedVelocity.vector.getY() > -0.02;
player.predictedVelocity.vectorY < 0.06 && player.predictedVelocity.vectorY > -0.02;
// Player ignored the knockback or is delaying it a tick... bad!
if (!player.predictedVelocity.isKnockback() && !lavaBugFix && player.getSetbackTeleportUtil().getRequiredSetBack().getVelocity() != null) {
if (!player.predictedVelocity.isKnockback() && !lavaBugFix && player.getSetbackTeleportUtil().getRequiredSetBack().isHasVelocity()) {
// And then send it again!
player.getSetbackTeleportUtil().executeForceResync();
}
@@ -567,7 +566,9 @@ public class MovementCheckRunner extends Check implements PositionCheck {
// Patch sprint jumping with elytra exploit
if (player.platformPlayer != null && player.isGliding && player.predictedVelocity.isJump() && player.isSprinting && !allowSprintJumpingWithElytra) {
SetbackTeleportUtil.SetbackPosWithVector lastKnownGoodPosition = player.getSetbackTeleportUtil().lastKnownGoodPosition;
lastKnownGoodPosition.setVector(lastKnownGoodPosition.getVector().multiply(new Vector3dm(0.6 * 0.91, 1, 0.6 * 0.91)));
lastKnownGoodPosition.setVectorX(lastKnownGoodPosition.getVectorX() * 0.6 * 0.91);
lastKnownGoodPosition.setVectorZ(lastKnownGoodPosition.getVectorZ() * 0.6 * 0.91);
player.getSetbackTeleportUtil().executeNonSimulatingSetback();
}
@@ -268,22 +268,21 @@ public class PointThreeEstimator {
isNearFluid = false;
// Check for flowing water
Collisions.hasMaterial(player, pointThreeBox, (pair) -> {
final WrappedBlockState state = pair.first();
Collisions.hasMaterial(player, pointThreeBox, (state, x, y, z) -> {
final StateType stateType = state.getType();
if (player.tagManager.block(SyncedTags.CLIMBABLE).contains(stateType) || (stateType == StateTypes.POWDER_SNOW && !player.inVehicle() && player.inventory.getBoots().getType() == ItemTypes.LEATHER_BOOTS)) {
isNearClimbable = true;
}
if (BlockTags.TRAPDOORS.contains(stateType)) {
isNearClimbable = isNearClimbable || Collisions.trapdoorUsableAsLadder(player, pair.second().getX(), pair.second().getY(), pair.second().getZ(), state);
isNearClimbable = isNearClimbable || Collisions.trapdoorUsableAsLadder(player, x, y, z, state);
}
if (stateType == StateTypes.BUBBLE_COLUMN && player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_13)) {
isNearBubbleColumn = true;
}
if (Materials.isWater(player.getClientVersion(), pair.first()) || pair.first().getType() == StateTypes.LAVA) {
if (Materials.isWater(player.getClientVersion(), state) || state.getType() == StateTypes.LAVA) {
isNearFluid = true;
}
@@ -365,7 +364,7 @@ public class PointThreeEstimator {
// If this tick is the tick after y velocity was by 0, a stepping movement is POSSIBLE to have been hidden
// A bit hacky... is there a better way? I'm unsure...
boolean likelyStepSkip = player.isPointThree() && (data.vector.getY() > -0.08 && data.vector.getY() < 0.06) && couldStep;
boolean likelyStepSkip = player.isPointThree() && (data.vectorY > -0.08 && data.vectorY < 0.06) && couldStep;
// We need to do hypot calculations for all 3 axis
// sqrt(sqrt(x^2 + z^2)^2 + y^2) = hypot(x, z, y)
@@ -411,7 +410,7 @@ public class PointThreeEstimator {
wasAlwaysCertain = false;
// Head hitters return the vector to 0, and then apply gravity to it.
// Not much room for abuse for this, so keep it lenient
return -Math.max(0, vector.vector.getY()) - 0.1 - fluidAddition;
return -Math.max(0, vector.vectorY) - 0.1 - fluidAddition;
} else if (player.uncertaintyHandler.wasAffectedByStuckSpeed()) {
wasAlwaysCertain = false;
// This shouldn't be needed but stuck speed can desync very easily with 0.03...
@@ -425,7 +424,7 @@ public class PointThreeEstimator {
double minMovement = player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) ? 0.003 : 0.005;
// This should likely be refactored, but it works well.
double yVel = vector.vector.getY();
double yVel = vector.vectorY;
double maxYTraveled = 0;
boolean first = true;
do {
@@ -452,7 +451,7 @@ public class PointThreeEstimator {
// We aren't making progress, avoid infinite loop (This can be due to the player not having gravity)
if (yVel == 0) break;
} while (Math.abs(maxYTraveled + vector.vector.getY()) < player.getMovementThreshold()); // Account for uncertainty, don't stop until we simulate past uncertainty point
} while (Math.abs(maxYTraveled + vector.vectorY) < player.getMovementThreshold()); // Account for uncertainty, don't stop until we simulate past uncertainty point
if (maxYTraveled != 0) {
wasAlwaysCertain = false;
@@ -66,13 +66,13 @@ public class SneakingEstimator extends Check implements PostPredictionCheck {
if (data.isJump() == player.predictedVelocity.isJump() && data.isKnockback() == player.predictedVelocity.isKnockback() && data.isExplosion() == player.predictedVelocity.isExplosion()) {
// Fuck, we are compounding this which is very dangerous. After light testing seems fine... can we do better?
if (player.uncertaintyHandler.lastStuckWest.hasOccurredSince(0) || player.uncertaintyHandler.lastStuckNorth.hasOccurredSince(0)) {
sneakingPotentialHiddenVelocity.minX = Math.min(sneakingPotentialHiddenVelocity.minX, data.vector.getX());
sneakingPotentialHiddenVelocity.minZ = Math.min(sneakingPotentialHiddenVelocity.minZ, data.vector.getZ());
sneakingPotentialHiddenVelocity.minX = Math.min(sneakingPotentialHiddenVelocity.minX, data.vectorX);
sneakingPotentialHiddenVelocity.minZ = Math.min(sneakingPotentialHiddenVelocity.minZ, data.vectorZ);
}
if (player.uncertaintyHandler.lastStuckEast.hasOccurredSince(0) || player.uncertaintyHandler.lastStuckSouth.hasOccurredSince(0)) {
sneakingPotentialHiddenVelocity.maxX = Math.max(sneakingPotentialHiddenVelocity.maxX, data.vector.getX());
sneakingPotentialHiddenVelocity.maxZ = Math.max(sneakingPotentialHiddenVelocity.maxZ, data.vector.getZ());
sneakingPotentialHiddenVelocity.maxX = Math.max(sneakingPotentialHiddenVelocity.maxX, data.vectorX);
sneakingPotentialHiddenVelocity.maxZ = Math.max(sneakingPotentialHiddenVelocity.maxZ, data.vectorZ);
}
}
}
@@ -157,12 +157,12 @@ public class UncertaintyHandler {
Vector3dm maxLocation = new Vector3dm(entityBox.maxX, entityBox.maxY, entityBox.maxZ);
Vector3dm minLocation = new Vector3dm(entityBox.minX, entityBox.minY, entityBox.minZ);
Vector3dm diff = minLocation.subtract(new Vector3dm(player.lastX, player.lastY + 0.8 * 1.8, player.lastZ)).multiply(0.1);
Vector3dm diff = minLocation.subtract(player.lastX, player.lastY + 0.8 * 1.8, player.lastZ).multiply(0.1);
fishingRodPullBox.minX = Math.min(0, diff.getX());
fishingRodPullBox.minY = Math.min(0, diff.getY());
fishingRodPullBox.minZ = Math.min(0, diff.getZ());
diff = maxLocation.subtract(new Vector3dm(player.lastX, player.lastY + 0.8 * 1.8, player.lastZ)).multiply(0.1);
diff = maxLocation.subtract(player.lastX, player.lastY + 0.8 * 1.8, player.lastZ).multiply(0.1);
fishingRodPullBox.maxX = Math.max(0, diff.getX());
fishingRodPullBox.maxY = Math.max(0, diff.getY());
fishingRodPullBox.maxZ = Math.max(0, diff.getZ());
@@ -264,7 +264,7 @@ public class UncertaintyHandler {
return 0.06;
// Not worth my time to fix this because checking flying generally sucks - if player was flying in last 2 ticks
if ((lastFlyingTicks.hasOccurredSince(5)) && Math.abs(data.vector.getY()) < (4.5 * player.flySpeed - 0.25))
if ((lastFlyingTicks.hasOccurredSince(5)) && Math.abs(data.vectorY) < (4.5 * player.flySpeed - 0.25))
return 0.06;
double pointThree = player.getMovementThreshold();
@@ -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()
);
}
}
@@ -0,0 +1,9 @@
package ac.grim.grimac.predictionengine.blockeffects;
import ac.grim.grimac.player.GrimPlayer;
public interface BlockEffectsResolver {
void applyEffectsFromBlocks(GrimPlayer player);
}
@@ -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);
}
@@ -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;
}
}
@@ -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));
}
}
}
}
}
}
}
@@ -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;
}
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -89,12 +89,12 @@ public class MovementTicker {
player.uncertaintyHandler.collidingEntities.add(possibleCollidingEntities);
}
private boolean isHorizontalCollisionSoft(Vector3dm collide) {
double horizontalLengthSquared = collide.getX() * collide.getX() + collide.getZ() * collide.getZ();
private boolean isHorizontalCollisionSoft(double x, double y, double z) {
double horizontalLengthSquared = x * x + z * z;
if (horizontalLengthSquared < 1E-5F) return false;
float xxa = (float) player.predictedVelocity.input.getX();
float zza = (float) player.predictedVelocity.input.getZ();
float xxa = (float) player.predictedVelocity.input.x();
float zza = (float) player.predictedVelocity.input.z();
float yawInRadians = player.yaw * (float) (Math.PI / 180.0);
double sin = player.trigHandler.sin(yawInRadians);
@@ -102,17 +102,17 @@ public class MovementTicker {
double g = xxa * cos - zza * sin;
double h = zza * cos + xxa * sin;
double i = g * g + h * h;
return i >= 1E-5F && Math.acos((g * collide.getX() + h * collide.getZ()) / Math.sqrt(i * horizontalLengthSquared)) < 0.13962634F;
return i >= 1E-5F && Math.acos((g * x + h * z) / Math.sqrt(i * horizontalLengthSquared)) < 0.13962634F;
}
public void move(Vector3dm inputVel, Vector3dm collide) {
public void move(Vector3dm inputVel, double x, double y, double z) {
if (player.stuckSpeedMultiplier.getX() < 0.99) {
player.clientVelocity = new Vector3dm();
}
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_18_2)) {
boolean xAxis = !GrimMath.equal(inputVel.getX(), collide.getX());
boolean zAxis = !GrimMath.equal(inputVel.getZ(), collide.getZ());
boolean xAxis = !GrimMath.equal(inputVel.getX(), x);
boolean zAxis = !GrimMath.equal(inputVel.getZ(), z);
if (xAxis) {
player.clientVelocity.setX(0);
@@ -123,20 +123,20 @@ public class MovementTicker {
}
player.horizontalCollision = xAxis || zAxis;
player.softHorizontalCollision = player.horizontalCollision && isHorizontalCollisionSoft(collide);
player.softHorizontalCollision = player.horizontalCollision && isHorizontalCollisionSoft(x, y, z);
} else {
if (inputVel.getX() != collide.getX()) {
if (inputVel.getX() != x) {
player.clientVelocity.setX(0);
}
if (inputVel.getZ() != collide.getZ()) {
if (inputVel.getZ() != z) {
player.clientVelocity.setZ(0);
}
player.horizontalCollision = inputVel.getX() != collide.getX() || inputVel.getZ() != collide.getZ();
player.horizontalCollision = inputVel.getX() != x || inputVel.getZ() != z;
}
player.verticalCollision = inputVel.getY() != collide.getY();
player.verticalCollision = inputVel.getY() != y;
// Avoid order of collisions being wrong because 0.03 movements
// Stepping movement USUALLY means the vehicle in on the ground as vehicles can't jump
@@ -145,7 +145,7 @@ public class MovementTicker {
boolean calculatedOnGround = (player.verticalCollision && inputVel.getY() < 0.0D);
// If the player is on the ground with a y velocity of 0, let the player decide (too close to call)
if (inputVel.getY() == -SimpleCollisionBox.COLLISION_EPSILON && collide.getY() > -SimpleCollisionBox.COLLISION_EPSILON && collide.getY() <= 0 && !player.inVehicle())
if (inputVel.getY() == -SimpleCollisionBox.COLLISION_EPSILON && y > -SimpleCollisionBox.COLLISION_EPSILON && y <= 0 && !player.inVehicle())
calculatedOnGround = player.onGround;
player.clientClaimsLastOnGround = player.onGround;
@@ -177,9 +177,9 @@ public class MovementTicker {
if (player.onGround) {
player.fallDistance = 0;
} else if (collide.getY() < 0) {
player.fallDistance = (player.fallDistance) - collide.getY();
player.vehicleData.lastYd = collide.getY();
} else if (y < 0) {
player.fallDistance = (player.fallDistance) - y;
player.vehicleData.lastYd = y;
}
// Striders call the method for inside blocks AGAIN!
@@ -187,11 +187,11 @@ public class MovementTicker {
Collisions.handleInsideBlocks(player);
}
player.mainSupportingBlockData = MainSupportingBlockPosFinder.findMainSupportingBlockPos(player, player.mainSupportingBlockData, new Vector3d(collide.getX(), collide.getY(), collide.getZ()), player.boundingBox, player.onGround);
player.mainSupportingBlockData = MainSupportingBlockPosFinder.findMainSupportingBlockPos(player, player.mainSupportingBlockData, new Vector3d(x, y, z), player.boundingBox, player.onGround);
StateType onBlock = BlockProperties.getOnPos(player, player.mainSupportingBlockData, new Vector3d(player.x, player.y, player.z));
// Hack with 1.14+ poses issue
if (inputVel.getY() != collide.getY()) {
if (inputVel.getY() != y) {
// If the client supports slime blocks
// And the block is a slime block
// Or the block is honey and was replaced by viaversion
@@ -214,26 +214,27 @@ public class MovementTicker {
player.clientVelocity.setY(0);
}
}
collide = PredictionEngine.clampMovementToHardBorder(player, collide);
// TODO reimplement? the comment about reimplementing clampMovementToHardBorder is 3 years old...
// collide = PredictionEngine.clampMovementToHardBorder(player, collide);
// The game disregards movements smaller than 1e-7 (such as in boats)
if (collide.lengthSquared() <= 1e-7
double collideLengthSquared = GrimMath.lengthSquared(x, y, z);
if (collideLengthSquared <= 1e-7
// New condition added in 1.21.2
&& (player.getClientVersion().isOlderThan(ClientVersion.V_1_21_2) || inputVel.lengthSquared() - collide.lengthSquared() >= 1e-7)) {
collide = new Vector3dm();
&& (player.getClientVersion().isOlderThan(ClientVersion.V_1_21_2) || inputVel.lengthSquared() - collideLengthSquared >= 1e-7)) {
x = y = z = 0;
} else if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_21_5)) {
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
player.predictedVelocity = new VectorData(collide.clone(), player.predictedVelocity.lastVector, player.predictedVelocity.vectorType);
player.predictedVelocity = new VectorData(x, y, z, player.predictedVelocity.lastVector, player.predictedVelocity.vectorType);
float f = BlockProperties.getBlockSpeedFactor(player, player.mainSupportingBlockData, new Vector3d(player.x, player.y, player.z));
player.clientVelocity.multiply(new Vector3dm(f, 1, f));
player.clientVelocity.multiply(f, 1, f);
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_21_2)) {
return;
@@ -306,10 +307,10 @@ public class MovementTicker {
// Meaning we should scan upwards!
oldBB.expand(-SimpleCollisionBox.COLLISION_EPSILON);
double posX = Math.max(0, player.predictedVelocity.vector.getX()) + SimpleCollisionBox.COLLISION_EPSILON;
double negX = Math.min(0, player.predictedVelocity.vector.getX()) - SimpleCollisionBox.COLLISION_EPSILON;
double posZ = Math.max(0, player.predictedVelocity.vector.getZ()) + SimpleCollisionBox.COLLISION_EPSILON;
double negZ = Math.min(0, player.predictedVelocity.vector.getZ()) - SimpleCollisionBox.COLLISION_EPSILON;
double posX = Math.max(0, player.predictedVelocity.vectorX) + SimpleCollisionBox.COLLISION_EPSILON;
double negX = Math.min(0, player.predictedVelocity.vectorX) - SimpleCollisionBox.COLLISION_EPSILON;
double posZ = Math.max(0, player.predictedVelocity.vectorZ) + SimpleCollisionBox.COLLISION_EPSILON;
double negZ = Math.min(0, player.predictedVelocity.vectorZ) - SimpleCollisionBox.COLLISION_EPSILON;
boolean xAxisCollision = !Collisions.isEmpty(player, oldBB.expandMin(negX, 0, 0).expandMax(posX, 0, 0));
boolean zAxisCollision = !Collisions.isEmpty(player, oldBB.expandMin(0, 0, negZ).expandMax(0, 0, posZ));
@@ -435,7 +436,7 @@ public class MovementTicker {
// 1.13 and below players can't climb ladders while touching water
// yes, 1.13 players cannot climb ladders underwater
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_14) && player.isClimbing) {
player.lastWasClimbing = FluidFallingAdjustedMovement.getFluidFallingAdjustedMovement(player, playerGravity, isFalling, player.clientVelocity.clone().setY(0.2D * 0.8F)).getY();
player.lastWasClimbing = FluidFallingAdjustedMovement.getAdjustedY(player, playerGravity, isFalling, 0.2D * 0.8F);
}
} else {
@@ -446,14 +447,14 @@ public class MovementTicker {
// Lava movement changed in 1.16
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_16) && player.slightlyTouchingLava) {
player.clientVelocity = player.clientVelocity.multiply(new Vector3dm(0.5D, 0.800000011920929D, 0.5D));
player.clientVelocity = FluidFallingAdjustedMovement.getFluidFallingAdjustedMovement(player, playerGravity, isFalling, player.clientVelocity);
player.clientVelocity = player.clientVelocity.multiply(0.5D, 0.800000011920929D, 0.5D);
player.clientVelocity.setY(FluidFallingAdjustedMovement.getAdjustedY(player, playerGravity, isFalling, player.clientVelocity.getY()));
} else {
player.clientVelocity.multiply(0.5D);
}
if (player.hasGravity)
player.clientVelocity.add(new Vector3dm(0.0D, -playerGravity / 4.0D, 0.0D));
player.clientVelocity.add(0.0D, -playerGravity / 4.0D, 0.0D);
} else if (player.isGliding) {
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_21_5) && Collisions.onClimbable(player, player.lastX, player.lastY, player.lastZ)) {
@@ -481,44 +482,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() {
@@ -30,7 +30,7 @@ public class MovementTickerStrider extends MovementTickerRideable {
if (isAbove(player) && player.compensatedWorld.getLavaFluidLevelAt((int) Math.floor(player.x), (int) Math.floor(player.y + 1), (int) Math.floor(player.z)) == 0) {
player.onGround = true;
} else {
player.clientVelocity.multiply(0.5).add(new Vector3dm(0, 0.05, 0));
player.clientVelocity.multiply(0.5).add(0, 0.05, 0);
}
}
}
@@ -9,6 +9,7 @@ import ac.grim.grimac.utils.data.KnownInput;
import ac.grim.grimac.utils.data.Pair;
import ac.grim.grimac.utils.data.VectorData;
import ac.grim.grimac.utils.math.GrimMath;
import ac.grim.grimac.utils.math.Vec2d;
import ac.grim.grimac.utils.math.Vector3dm;
import ac.grim.grimac.utils.math.VectorUtils;
import ac.grim.grimac.utils.nmsutil.Collisions;
@@ -31,22 +32,22 @@ public class PredictionEngine {
return outputVel;
}
public static Vector3dm transformInputsToVector(GrimPlayer player, Vector3dm theoreticalInput) {
public static Vec2d transformInputsToVector(GrimPlayer player, int strafe, int forward) {
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_21_5)) {
Vec2 moveVector = new Vec2((float) theoreticalInput.getX(), (float) theoreticalInput.getZ()).normalized();
Vec2 moveVector = new Vec2((float) strafe, (float) forward).normalized();
Vec2 input = modifyInput(player, moveVector);
return new Vector3dm(input.x(), 0, input.y());
return new Vec2d(input.x(), input.y());
}
float bestPossibleX;
float bestPossibleZ;
// Slow movement was determined by the previous pose
if (player.isSlowMovement) {
bestPossibleX = (float) (theoreticalInput.getX() * player.sneakingSpeedMultiplier);
bestPossibleZ = (float) (theoreticalInput.getZ() * player.sneakingSpeedMultiplier);
bestPossibleX = (float) (strafe * player.sneakingSpeedMultiplier);
bestPossibleZ = (float) (forward * player.sneakingSpeedMultiplier);
} else {
bestPossibleX = Math.min(Math.max(-1f, Math.round(theoreticalInput.getX())), 1f);
bestPossibleZ = Math.min(Math.max(-1f, Math.round(theoreticalInput.getZ())), 1f);
bestPossibleX = Math.min(Math.max(-1f, strafe), 1f);
bestPossibleZ = Math.min(Math.max(-1f, forward), 1f);
}
if (player.packetStateData.isSlowedByUsingItem()) {
@@ -54,18 +55,23 @@ public class PredictionEngine {
bestPossibleZ *= 0.2F;
}
Vector3dm inputVector = new Vector3dm(bestPossibleX, 0, bestPossibleZ);
inputVector.multiply(0.98F);
double inputVectorX = bestPossibleX;
double inputVectorZ = bestPossibleZ;
inputVectorX *= 0.98F;
inputVectorZ *= 0.98F;
// Simulate float rounding imprecision
inputVector = new Vector3dm((float) inputVector.getX(), (float) inputVector.getY(), (float) inputVector.getZ());
inputVectorX = (float) inputVectorX;
inputVectorZ = (float) inputVectorZ;
if (inputVector.lengthSquared() > 1) {
double d0 = Math.sqrt(inputVector.getX() * inputVector.getX() + inputVector.getY() * inputVector.getY() + inputVector.getZ() * inputVector.getZ());
inputVector = new Vector3dm(inputVector.getX() / d0, inputVector.getY() / d0, inputVector.getZ() / d0);
double lengthSquared = GrimMath.lengthSquared(inputVectorX, inputVectorZ);
if (lengthSquared > 1) {
double d0 = Math.sqrt(lengthSquared);
inputVectorX = inputVectorX / d0;
inputVectorZ = inputVectorZ / d0;
}
return inputVector;
return new Vec2d(inputVectorX, inputVectorZ);
}
public static Vec2 modifyInput(GrimPlayer player, Vec2 moveVector) {
@@ -140,7 +146,7 @@ public class PredictionEngine {
// Client velocity - before collision and carried into the next tick
// Predicted velocity - after collision and not carried into the next tick
new MovementTickerPlayer(player).move(player.clientVelocity.clone(), player.predictedVelocity.vector);
new MovementTickerPlayer(player).move(player.clientVelocity.clone(), player.predictedVelocity.vectorX, player.predictedVelocity.vectorY, player.predictedVelocity.vectorZ);
endOfTick(player, player.gravity);
}
@@ -234,7 +240,7 @@ public class PredictionEngine {
beforeCollisionMovement = primaryPushMovement;
// We basically want to avoid falsing ground spoof, try to find a vector that works
if (player.wouldCollisionResultFlagGroundSpoof(primaryPushMovement.getY(), bestCollisionVel.vector.getY()))
if (player.wouldCollisionResultFlagGroundSpoof(primaryPushMovement.getY(), bestCollisionVel.vectorY))
resultAccuracy += 0.0001 * 0.0001;
bestInput = resultAccuracy;
@@ -259,7 +265,7 @@ public class PredictionEngine {
}
private Pair<Vector3dm, Vector3dm> doSeekingWallCollisions(GrimPlayer player, Vector3dm primaryPushMovement, Vector3dm originalClientVel, VectorData clientVelAfterInput) {
boolean vehicleKB = player.inVehicle() && clientVelAfterInput.isKnockback() && clientVelAfterInput.vector.getY() == 0;
boolean vehicleKB = player.inVehicle() && clientVelAfterInput.isKnockback() && clientVelAfterInput.vectorY == 0;
// Extra collision epsilon required for vehicles to be accurate
double xAdditional = Math.signum(primaryPushMovement.getX()) * SimpleCollisionBox.COLLISION_EPSILON;
// The server likes sending y=0 kb "lifting" the player off the ground.
@@ -329,10 +335,9 @@ public class PredictionEngine {
player.clientVelocity.getX(), 0, player.clientVelocity.getZ()).expand(0.5, -SimpleCollisionBox.COLLISION_EPSILON, 0.5)))) {
// Calculate the Y velocity after friction
Vector3dm hackyClimbVector = new Vector3dm(0, 0.2, 0);
PredictionEngineNormal.staticVectorEndOfTick(player, hackyClimbVector);
double y = PredictionEngineNormal.staticVectorEndOfTickY(player, 0.2);
pointThreePossibilities.add(new VectorData(hackyClimbVector, VectorData.VectorType.ZeroPointZeroThree));
pointThreePossibilities.add(new VectorData(0, y, 0, VectorData.VectorType.ZeroPointZeroThree));
}
// This is a secure method to add jumping vectors to this list
@@ -357,19 +362,24 @@ public class PredictionEngine {
for (VectorData vectorData : data) {
// Sneaking in water
if (vectorData.isKnockback() && player.baseTickAddition.lengthSquared() != 0) {
vectorData.vector = vectorData.vector.add(player.baseTickAddition);
vectorData.vectorX += player.baseTickAddition.getX();
vectorData.vectorY += player.baseTickAddition.getY();
vectorData.vectorZ += player.baseTickAddition.getZ();
}
// Water pushing movement is affected by initial velocity due to 0.003 eating pushing in the past
if (vectorData.isKnockback() && player.baseTickWaterPushing.lengthSquared() != 0) {
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_13)) {
Vector3dm vec3 = player.baseTickWaterPushing.clone();
if (Math.abs(vectorData.vector.getX()) < 0.003 && Math.abs(vectorData.vector.getZ()) < 0.003 && player.baseTickWaterPushing.length() < 0.0045000000000000005D) {
if (Math.abs(vectorData.vectorX) < 0.003 && Math.abs(vectorData.vectorZ) < 0.003 && player.baseTickWaterPushing.length() < 0.0045000000000000005D) {
vec3 = vec3.normalize().multiply(0.0045000000000000005);
}
vectorData.vector = vectorData.vector.add(vec3);
vectorData.vectorX += vec3.getX();
vectorData.vectorY += vec3.getY();
vectorData.vectorZ += vec3.getZ();
} else {
vectorData.vector = vectorData.vector.add(player.baseTickWaterPushing);
vectorData.vectorX += player.baseTickWaterPushing.getX();
vectorData.vectorY += player.baseTickWaterPushing.getY();
vectorData.vectorZ += player.baseTickWaterPushing.getZ();
}
}
}
@@ -405,7 +415,9 @@ public class PredictionEngine {
if (!player.inVehicle() || player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_21_5)) return;
for (VectorData vectorData : data) {
vectorData.vector = vectorData.vector.clone().multiply(0.98);
vectorData.vectorX = vectorData.vectorX * 0.98;
vectorData.vectorY = vectorData.vectorY * 0.98;
vectorData.vectorZ = vectorData.vectorZ * 0.98;
}
}
@@ -413,11 +425,11 @@ public class PredictionEngine {
for (int x = 1; x <= Math.min(player.maxAttackSlow, 5); x++) {
for (VectorData data : new HashSet<>(velocities)) {
if (player.minAttackSlow > 0) {
data.vector.setX(data.vector.getX() * 0.6);
data.vector.setZ(data.vector.getZ() * 0.6);
data.vectorX = data.vectorX * 0.6;
data.vectorZ = data.vectorZ * 0.6;
data.addVectorType(VectorData.VectorType.AttackSlow);
} else {
velocities.add(data.returnNewModified(data.vector.clone().multiply(new Vector3dm(0.6, 1, 0.6)), VectorData.VectorType.AttackSlow));
velocities.add(data.returnNewModified(data.vectorX * 0.6, data.vectorY, data.vectorZ * 0.6, VectorData.VectorType.AttackSlow));
}
}
@@ -437,37 +449,61 @@ public class PredictionEngine {
minimumMovement = 0.005D;
}
for (VectorData vector : velocities) {
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_21_5) && !player.inVehicle()) {
if (Collisions.getHorizontalDistanceSqr(vector.vector) < 9.0E-6) {
vector.vector.setX(0D);
vector.vector.setZ(0D);
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.vectorX, vector.vectorZ) < 9.0E-6) {
if (stuckOnEdge) {
VectorData edgeVector = vector.returnNewModified(vector.vectorX, vector.vectorY, vector.vectorZ, vector.vectorType);
if (Math.abs(edgeVector.vectorY) < minimumMovement) {
edgeVector.setY(0D);
}
velocities.add(edgeVector);
}
vector.setX(0D);
vector.setZ(0D);
}
} else {
if (Math.abs(vector.vector.getX()) < minimumMovement) {
vector.vector.setX(0D);
if (Math.abs(vector.vectorX) < minimumMovement) {
vector.setX(0D);
}
if (Math.abs(vector.vector.getZ()) < minimumMovement) {
vector.vector.setZ(0D);
if (Math.abs(vector.vectorZ) < minimumMovement) {
vector.setZ(0D);
}
}
if (Math.abs(vector.vector.getY()) < minimumMovement) {
vector.vector.setY(0D);
if (Math.abs(vector.vectorY) < minimumMovement) {
vector.setY(0D);
}
}
}
public void addExplosionToPossibilities(GrimPlayer player, Set<VectorData> existingVelocities) {
for (VectorData vector : new HashSet<>(existingVelocities)) {
if (player.likelyExplosions == null && player.firstBreadExplosion == null) return;
final VectorData[] vectorsToProcess = existingVelocities.toArray(new VectorData[0]);
for (VectorData vector : vectorsToProcess) {
if (player.likelyExplosions != null) {
existingVelocities.add(new VectorData(vector.vector.clone().add(player.likelyExplosions.vector), vector, VectorData.VectorType.Explosion));
final double newX = player.likelyExplosions.vector.getX() + vector.vectorX;
final double newY = player.likelyExplosions.vector.getY() + vector.vectorY;
final double newZ = player.likelyExplosions.vector.getZ() + vector.vectorZ;
existingVelocities.add(new VectorData(newX, newY, newZ, vector, VectorData.VectorType.Explosion));
}
if (player.firstBreadExplosion != null) {
existingVelocities.add(new VectorData(vector.vector.clone().add(player.firstBreadExplosion.vector), vector, VectorData.VectorType.Explosion)
.returnNewModified(vector.vector.clone().add(player.firstBreadExplosion.vector), VectorData.VectorType.FirstBreadExplosion));
// Previously we called Vector3dm.add(player.likelyExplosions.vector) after checking (player.firstBreadExplosion != null)
// which would've thrown an NPE if player.likelyExplosions could be null here, we can ignore this
// empirically this means when firstBreadExplosion != null then likelyExplosions is also != null
final double newX = player.likelyExplosions.vector.getX() + vector.vectorX;
final double newY = player.likelyExplosions.vector.getY() + vector.vectorY;
final double newZ = player.likelyExplosions.vector.getZ() + vector.vectorZ;
VectorData intermediateData = new VectorData(newX, newY, newZ, vector, VectorData.VectorType.Explosion);
VectorData finalData = new VectorData(newX, newY, newZ, intermediateData, VectorData.VectorType.FirstBreadExplosion);
existingVelocities.add(finalData);
}
}
}
@@ -520,16 +556,18 @@ public class PredictionEngine {
bScore -= 1;
// If the player is on the ground but the vector leads the player off the ground
if ((player.inVehicle() ? player.clientControlledVerticalCollision : player.onGround) && a.vector.getY() >= 0)
if ((player.inVehicle() ? player.clientControlledVerticalCollision : player.onGround) && a.vectorY >= 0)
aScore += 2;
if ((player.inVehicle() ? player.clientControlledVerticalCollision : player.onGround) && b.vector.getY() >= 0)
if ((player.inVehicle() ? player.clientControlledVerticalCollision : player.onGround) && b.vectorY >= 0)
bScore += 2;
if (aScore != bScore)
return Integer.compare(aScore, bScore);
return Double.compare(a.vector.distanceSquared(player.actualMovement), b.vector.distanceSquared(player.actualMovement));
return Double.compare(
GrimMath.distanceSquared(player.actualMovement.getX(), player.actualMovement.getY(), player.actualMovement.getZ(), a.vectorX, a.vectorY, a.vectorZ),
GrimMath.distanceSquared(player.actualMovement.getX(), player.actualMovement.getY(), player.actualMovement.getZ(), b.vectorX, b.vectorY, b.vectorZ));
}
public Vector3dm handleStartingVelocityUncertainty(GrimPlayer player, VectorData vector, Vector3dm targetVec) {
@@ -583,69 +621,81 @@ public class PredictionEngine {
// 0.075 seems safe?
//
// Be somewhat careful as there is an antikb (for horizontal) that relies on this lenience
Vector3dm uncertainty = new Vector3dm(avgColliding * 0.08, additionVertical, avgColliding * 0.08);
final double uncertaintyX = avgColliding * 0.08;
final double uncertaintyY = additionVertical;
final double uncertaintyZ = avgColliding * 0.08;
Vector3dm min = new Vector3dm(player.uncertaintyHandler.xNegativeUncertainty - additionHorizontal, -bonusY + player.uncertaintyHandler.yNegativeUncertainty, player.uncertaintyHandler.zNegativeUncertainty - additionHorizontal);
Vector3dm max = new Vector3dm(player.uncertaintyHandler.xPositiveUncertainty + additionHorizontal, bonusY + player.uncertaintyHandler.yPositiveUncertainty, player.uncertaintyHandler.zPositiveUncertainty + additionHorizontal);
final double minUncertaintyX = player.uncertaintyHandler.xNegativeUncertainty - additionHorizontal;
final double minUncertaintyY = player.uncertaintyHandler.yNegativeUncertainty - bonusY;
final double minUncertaintyZ = player.uncertaintyHandler.zNegativeUncertainty - additionHorizontal;
Vector3dm minVector = vector.vector.clone().add(min.subtract(uncertainty));
Vector3dm maxVector = vector.vector.clone().add(max.add(uncertainty));
final double maxUncertaintyX = player.uncertaintyHandler.xPositiveUncertainty + additionHorizontal;
final double maxUncertaintyY = player.uncertaintyHandler.yPositiveUncertainty + bonusY;
final double maxUncertaintyZ = player.uncertaintyHandler.zPositiveUncertainty + additionHorizontal;
double minVecX = vector.vectorX + (minUncertaintyX - uncertaintyX);
double minVecY = vector.vectorY + (minUncertaintyY - uncertaintyY);
double minVecZ = vector.vectorZ + (minUncertaintyZ - uncertaintyZ);
double maxVecX = vector.vectorX + (maxUncertaintyX + uncertaintyX);
double maxVecY = vector.vectorY + (maxUncertaintyY + uncertaintyY);
double maxVecZ = vector.vectorZ + (maxUncertaintyZ + uncertaintyZ);
// Handle the player landing within 0.03 movement, which resets Y velocity
if (player.uncertaintyHandler.onGroundUncertain && vector.vector.getY() < 0) {
maxVector.setY(0);
if (player.uncertaintyHandler.onGroundUncertain && vector.vectorY < 0) {
maxVecY = 0;
}
// Handles stuff like missing idle packet causing gravity to be missed (plus 0.03 of course)
double gravityOffset = player.pointThreeEstimator.getAdditionalVerticalUncertainty(vector);
if (gravityOffset > 0) {
maxVector.setY(maxVector.getY() + gravityOffset);
maxVecY += gravityOffset;
} else {
minVector.setY(minVector.getY() + gravityOffset);
minVecY += gravityOffset;
}
// Handle vertical fluid pushing within 0.03
double verticalFluid = player.pointThreeEstimator.getVerticalFluidPushingUncertainty(vector);
minVector.setY(minVector.getY() - verticalFluid);
minVecY -= verticalFluid;
// Handle vertical bubble column stupidity within 0.03
double bubbleFluid = player.pointThreeEstimator.getVerticalBubbleUncertainty(vector);
maxVector.setY(maxVector.getY() + bubbleFluid);
minVector.setY(minVector.getY() - bubbleFluid);
maxVecY += bubbleFluid;
minVecY -= bubbleFluid;
// We can't simulate the player's Y velocity, unknown number of ticks with a gravity change
// Feel free to simulate all 104857600000000000000000000 possibilities!
if (!player.pointThreeEstimator.canPredictNextVerticalMovement()) {
minVector.setY(minVector.getY() - player.compensatedEntities.self.getAttributeValue(Attributes.GRAVITY));
minVecY -= player.compensatedEntities.self.getAttributeValue(Attributes.GRAVITY);
}
// Hidden slime block bounces by missing idle tick and 0.03
if (player.actualMovement.getY() >= 0 && player.uncertaintyHandler.influencedByBouncyBlock()) {
if (player.uncertaintyHandler.thisTickSlimeBlockUncertainty != 0 && !vector.isJump()) { // jumping overrides slime block
if (player.uncertaintyHandler.thisTickSlimeBlockUncertainty > maxVector.getY()) {
maxVector.setY(player.uncertaintyHandler.thisTickSlimeBlockUncertainty);
if (player.uncertaintyHandler.thisTickSlimeBlockUncertainty > maxVecY) {
maxVecY = player.uncertaintyHandler.thisTickSlimeBlockUncertainty;
}
if (minVector.getY() > 0) minVector.setY(0);
if (minVecY > 0) minVecY = 0;
}
}
if (vector.isZeroPointZeroThree() && vector.isSwimHop()) {
minVector.setY(minVector.getY() - 0.06); // Fluid pushing downwards hidden by 0.03
minVecY -= 0.06; // Fluid pushing downwards hidden by 0.03
}
SimpleCollisionBox box = new SimpleCollisionBox(minVector, maxVector);
SimpleCollisionBox box = new SimpleCollisionBox(minVecX, minVecY, minVecZ, maxVecX, maxVecY, maxVecZ);
box.sort();
// https://github.com/MWHunter/Grim/issues/398
// Thank mojang for removing the idle packet resulting in this hacky mess
double levitation = player.pointThreeEstimator.positiveLevitation(maxVector.getY());
double levitation = player.pointThreeEstimator.positiveLevitation(maxVecY);
box.combineToMinimum(box.minX, levitation, box.minZ);
levitation = player.pointThreeEstimator.positiveLevitation(minVector.getY());
levitation = player.pointThreeEstimator.positiveLevitation(minVecY);
box.combineToMinimum(box.minX, levitation, box.minZ);
levitation = player.pointThreeEstimator.negativeLevitation(maxVector.getY());
levitation = player.pointThreeEstimator.negativeLevitation(maxVecY);
box.combineToMinimum(box.minX, levitation, box.minZ);
levitation = player.pointThreeEstimator.negativeLevitation(minVector.getY());
levitation = player.pointThreeEstimator.negativeLevitation(minVecY);
box.combineToMinimum(box.minX, levitation, box.minZ);
@@ -656,12 +706,12 @@ public class PredictionEngine {
box.maxZ += sneaking.getSneakingPotentialHiddenVelocity().maxZ;
if (player.uncertaintyHandler.fireworksBox != null) {
double minXdiff = Math.min(0, player.uncertaintyHandler.fireworksBox.minX - originalVec.vector.getX());
double minYdiff = Math.min(0, player.uncertaintyHandler.fireworksBox.minY - originalVec.vector.getY());
double minZdiff = Math.min(0, player.uncertaintyHandler.fireworksBox.minZ - originalVec.vector.getZ());
double maxXdiff = Math.max(0, player.uncertaintyHandler.fireworksBox.maxX - originalVec.vector.getX());
double maxYdiff = Math.max(0, player.uncertaintyHandler.fireworksBox.maxY - originalVec.vector.getY());
double maxZdiff = Math.max(0, player.uncertaintyHandler.fireworksBox.maxZ - originalVec.vector.getZ());
double minXdiff = Math.min(0, player.uncertaintyHandler.fireworksBox.minX - originalVec.vectorX);
double minYdiff = Math.min(0, player.uncertaintyHandler.fireworksBox.minY - originalVec.vectorY);
double minZdiff = Math.min(0, player.uncertaintyHandler.fireworksBox.minZ - originalVec.vectorZ);
double maxXdiff = Math.max(0, player.uncertaintyHandler.fireworksBox.maxX - originalVec.vectorX);
double maxYdiff = Math.max(0, player.uncertaintyHandler.fireworksBox.maxY - originalVec.vectorY);
double maxZdiff = Math.max(0, player.uncertaintyHandler.fireworksBox.maxZ - originalVec.vectorZ);
box.expandMin(minXdiff, minYdiff, minZdiff);
box.expandMax(maxXdiff, maxYdiff, maxZdiff);
@@ -714,7 +764,7 @@ public class PredictionEngine {
//
// Or the player is switching in and out of controlling a vehicle, in which friction messes it up
//
if (player.uncertaintyHandler.lastVehicleSwitch.hasOccurredSince(0) || player.uncertaintyHandler.lastHardCollidingLerpingEntity.hasOccurredSince(3) || (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_13) && vector.vector.getY() > 0 && vector.isZeroPointZeroThree() && !Collisions.isEmpty(player, GetBoundingBox.getBoundingBoxFromPosAndSize(player, player.lastX, vector.vector.getY() + player.lastY + 0.6, player.lastZ, 0.6f, 1.26f)))) {
if (player.uncertaintyHandler.lastVehicleSwitch.hasOccurredSince(0) || player.uncertaintyHandler.lastHardCollidingLerpingEntity.hasOccurredSince(3) || (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_13) && vector.vectorY > 0 && vector.isZeroPointZeroThree() && !Collisions.isEmpty(player, GetBoundingBox.getBoundingBoxFromPosAndSize(player, player.lastX, vector.vectorY + player.lastY + 0.6, player.lastZ, 0.6f, 1.26f)))) {
box.expandToAbsoluteCoordinates(0, 0, 0);
}
@@ -740,22 +790,26 @@ public class PredictionEngine {
box.expand(0.001); // Ignore 1e-3 offsets as we don't know starting vel
}
minVector = box.min();
maxVector = box.max();
minVecX = box.minX;
minVecY = box.minY;
minVecZ = box.minZ;
maxVecX = box.maxX;
maxVecY = box.maxY;
maxVecZ = box.maxZ;
if (pistonX != 0) {
minVector.setX(Math.min(minVector.getX() - pistonX, pistonX));
maxVector.setX(Math.max(maxVector.getX() + pistonX, pistonX));
minVecX = Math.min(minVecX - pistonX, pistonX);
maxVecX = Math.max(maxVecX + pistonX, pistonX);
}
if (pistonY != 0) {
minVector.setY(Math.min(minVector.getY() - pistonY, pistonY));
maxVector.setY(Math.max(maxVector.getY() + pistonY, pistonY));
minVecY = Math.min(minVecY - pistonY, pistonY);
maxVecY = Math.max(maxVecY + pistonY, pistonY);
}
if (pistonZ != 0) {
minVector.setZ(Math.min(minVector.getZ() - pistonZ, pistonZ));
maxVector.setZ(Math.max(maxVector.getZ() + pistonZ, pistonZ));
minVecZ = Math.min(minVecZ - pistonZ, pistonZ);
maxVecZ = Math.max(maxVecZ + pistonZ, pistonZ);
}
return VectorUtils.cutBoxToVector(targetVec, minVector, maxVector);
return VectorUtils.cutBoxToVector(targetVec, minVecX, minVecY, minVecZ, maxVecX, maxVecY, maxVecZ);
}
public void endOfTick(GrimPlayer player, double d) {
@@ -815,15 +869,20 @@ public class PredictionEngine {
for (int applyStuckSpeed = 1; applyStuckSpeed >= 0; applyStuckSpeed--) {
if (applyStuckSpeed == 0 && player.isForceStuckSpeed()) break;
Vector3dm input = transformInputsToVector(player, new Vector3dm(strafe, 0, forward));
VectorData result = new VectorData(possibleLastTickOutput.vector.clone()
.add(getMovementResultFromInput(player, input, speed, player.yaw)),
Vec2d input = transformInputsToVector(player, strafe, forward);
Vector3dm movementResultFromInput = getMovementResultFromInput(player, input.x(), 0, input.z(), speed, player.yaw);
VectorData result = new VectorData(possibleLastTickOutput.vectorX + movementResultFromInput.getX(),
possibleLastTickOutput.vectorY + movementResultFromInput.getY(),
possibleLastTickOutput.vectorZ + movementResultFromInput.getZ(),
possibleLastTickOutput, VectorData.VectorType.InputResult);
result.input = input;
if (applyStuckSpeed != 0) {
result = result.returnNewModified(result.vector.clone().multiply(player.stuckSpeedMultiplier), VectorData.VectorType.StuckMultiplier);
result = result.returnNewModified(result.vectorX * player.stuckSpeedMultiplier.getX(),
result.vectorY * player.stuckSpeedMultiplier.getY(),
result.vectorZ * player.stuckSpeedMultiplier.getZ(),
VectorData.VectorType.StuckMultiplier);
}
result = result.returnNewModified(handleOnClimbable(result.vector.clone(), player), VectorData.VectorType.Climbable);
result = result.returnNewModified(handleOnClimbable(result.vectorX, result.vectorY, result.vectorZ, player), VectorData.VectorType.Climbable);
// Signal that we need to flip sneaking bounding box
if (loopUsingItem == 1)
result = result.returnNewModified(VectorData.VectorType.Flip_Use_Item);
@@ -889,25 +948,23 @@ public class PredictionEngine {
// This is just the vanilla equation, which accepts invalid inputs greater than 1
// We need it because of collision support when a player is using speed
public Vector3dm getMovementResultFromInput(GrimPlayer player, Vector3dm inputVector, float f, float f2) {
float f2InRadians = GrimMath.radians(f2);
public Vector3dm getMovementResultFromInput(GrimPlayer player, double x, double y, double z, float speed, float yaw) {
float f2InRadians = GrimMath.radians(yaw);
float f3 = player.trigHandler.sin(f2InRadians);
float f4 = player.trigHandler.cos(f2InRadians);
double xResult = inputVector.getX() * f4 - inputVector.getZ() * f3;
double zResult = inputVector.getZ() * f4 + inputVector.getX() * f3;
double xResult = x * f4 - z * f3;
double zResult = z * f4 + x * f3;
return new Vector3dm(xResult * f, 0, zResult * f);
return new Vector3dm(xResult * speed, 0, zResult * speed);
}
public Vector3dm handleOnClimbable(Vector3dm vector, GrimPlayer player) {
return vector;
public Vector3dm handleOnClimbable(double x, double y, double z, GrimPlayer player) {
return new Vector3dm(x, y, z);
}
public void doJump(GrimPlayer player, Vector3dm vector) {
if (!player.lastOnGround || player.onGround)
return;
JumpPower.jumpFromGround(player, vector);
public void doJump(GrimPlayer player, double[] vectorArr) {
if (!player.lastOnGround || player.onGround) return;
JumpPower.jumpFromGround(player, vectorArr);
}
}
@@ -3,6 +3,7 @@ package ac.grim.grimac.predictionengine.predictions;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.data.VectorData;
import ac.grim.grimac.utils.math.GrimMath;
import ac.grim.grimac.utils.math.Vec2d;
import ac.grim.grimac.utils.math.Vector3dm;
import ac.grim.grimac.utils.nmsutil.ReachUtils;
import com.github.retrooper.packetevents.protocol.attribute.Attributes;
@@ -13,11 +14,13 @@ import java.util.List;
import java.util.Set;
public class PredictionEngineElytra extends PredictionEngine {
public static Vector3dm getElytraMovement(GrimPlayer player, Vector3dm vector, Vector3dm lookVector) {
public static void applyElytraMovementInPlace(GrimPlayer player,
double[] vectorArr,
double lookX, double lookY, double lookZ) {
float pitchRadians = GrimMath.radians(player.pitch);
double horizontalSqrt = Math.sqrt(lookVector.getX() * lookVector.getX() + lookVector.getZ() * lookVector.getZ());
double horizontalLength = vector.clone().setY(0).length();
double length = lookVector.length();
double horizontalSqrt = Math.sqrt(lookX * lookX + lookZ * lookZ);
double horizontalLength = GrimMath.length(vectorArr[0], 0, vectorArr[2]);
double length = GrimMath.length(lookX, lookY, lookZ);
// Mojang changed from using their math to using regular java math in 1.18.2 elytra movement
double vertCosRotation = player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_18_2) ? Math.cos(pitchRadians) : player.trigHandler.cos(pitchRadians);
@@ -31,27 +34,30 @@ public class PredictionEngineElytra extends PredictionEngine {
recalculatedGravity = player.getClientVersion().isOlderThan(ClientVersion.V_1_20_5) ? 0.01 : Math.min(recalculatedGravity, 0.01);
}
vector.add(new Vector3dm(0.0D, recalculatedGravity * (-1.0D + vertCosRotation * 0.75D), 0.0D));
vectorArr[1] += recalculatedGravity * (-1.0D + vertCosRotation * 0.75D);
double d5;
// Handle slowing the player down when falling
if (vector.getY() < 0.0D && horizontalSqrt > 0.0D) {
d5 = vector.getY() * -0.1D * vertCosRotation;
vector.add(new Vector3dm(lookVector.getX() * d5 / horizontalSqrt, d5, lookVector.getZ() * d5 / horizontalSqrt));
if (vectorArr[1] < 0.0D && horizontalSqrt > 0.0D) {
d5 = vectorArr[1] * -0.1D * vertCosRotation;
vectorArr[0] += lookX * d5 / horizontalSqrt;
vectorArr[1] += d5;
vectorArr[2] += lookZ * d5 / horizontalSqrt;
}
// Handle accelerating the player when they are looking down
if (pitchRadians < 0.0F && horizontalSqrt > 0.0D) {
d5 = horizontalLength * (double) (-player.trigHandler.sin(pitchRadians)) * 0.04D;
vector.add(new Vector3dm(-lookVector.getX() * d5 / horizontalSqrt, d5 * 3.2D, -lookVector.getZ() * d5 / horizontalSqrt));
vectorArr[0] += -lookX * d5 / horizontalSqrt;
vectorArr[1] += d5 * 3.2D;
vectorArr[2] += -lookZ * d5 / horizontalSqrt;
}
// Handle accelerating the player sideways
if (horizontalSqrt > 0) {
vector.add(new Vector3dm((lookVector.getX() / horizontalSqrt * horizontalLength - vector.getX()) * 0.1D, 0.0D, (lookVector.getZ() / horizontalSqrt * horizontalLength - vector.getZ()) * 0.1D));
vectorArr[0] += (lookX / horizontalSqrt * horizontalLength - vectorArr[0]) * 0.1D;
vectorArr[2] += (lookZ / horizontalSqrt * horizontalLength - vectorArr[2]) * 0.1D;
}
return vector;
}
// Inputs have no effect on movement
@@ -65,11 +71,19 @@ public class PredictionEngineElytra extends PredictionEngine {
for (int applyStuckSpeed = 1; applyStuckSpeed >= 0; applyStuckSpeed--) {
if (applyStuckSpeed == 0 && player.isForceStuckSpeed()) break;
for (VectorData data : possibleVectors) {
Vector3dm elytraResult = getElytraMovement(player, data.vector.clone(), currentLook);
if (applyStuckSpeed != 0) elytraResult.multiply(player.stuckSpeedMultiplier);
elytraResult.multiply(new Vector3dm(0.99F, 0.98F, 0.99F));
VectorData modified = data.returnNewModified(elytraResult, VectorData.VectorType.InputResult);
modified.input = new Vector3dm(0, 0, 0);
double[] vector = {data.vectorX, data.vectorY, data.vectorZ};
applyElytraMovementInPlace(player, vector, currentLook.getX(), currentLook.getY(), currentLook.getZ());
if (applyStuckSpeed != 0) {
vector[0] *= player.stuckSpeedMultiplier.getX();
vector[1] *= player.stuckSpeedMultiplier.getY();
vector[2] *= player.stuckSpeedMultiplier.getZ();
}
vector[0] *= 0.99F;
vector[1] *= 0.98F;
vector[2] *= 0.99f;
VectorData modified = data.returnNewModified(vector[0], vector[1], vector[2], VectorData.VectorType.InputResult);
modified.input = new Vec2d(0, 0);
results.add(modified);
}
}
@@ -11,18 +11,23 @@ import java.util.Set;
public class PredictionEngineLava extends PredictionEngine {
@Override
public void addJumpsToPossibilities(GrimPlayer player, Set<VectorData> existingVelocities) {
for (VectorData vector : new HashSet<>(existingVelocities)) {
final VectorData[] vectorsToProcess = existingVelocities.toArray(new VectorData[0]);
for (VectorData vector : vectorsToProcess) {
final double startX = vector.vectorX;
final double startY = vector.vectorY;
final double startZ = vector.vectorZ;
if (player.couldSkipTick && vector.isZeroPointZeroThree()) {
double extraVelFromVertTickSkipUpwards = GrimMath.clamp(player.actualMovement.getY(), vector.vector.clone().getY(), vector.vector.clone().getY() + 0.05f);
existingVelocities.add(new VectorData(vector.vector.clone().setY(extraVelFromVertTickSkipUpwards), vector, VectorData.VectorType.Jump));
double extraVelFromVertTickSkipUpwards = GrimMath.clamp(player.actualMovement.getY(), startY, startY + 0.05f);
existingVelocities.add(new VectorData(startX, extraVelFromVertTickSkipUpwards, startZ, vector, VectorData.VectorType.Jump));
} else {
existingVelocities.add(new VectorData(vector.vector.clone().add(new Vector3dm(0, 0.04f, 0)), vector, VectorData.VectorType.Jump));
existingVelocities.add(new VectorData(startX, startY + 0.04f, startZ, vector, VectorData.VectorType.Jump));
}
if (player.slightlyTouchingLava && player.lastOnGround && !player.onGround) {
Vector3dm withJump = vector.vector.clone();
super.doJump(player, withJump);
existingVelocities.add(new VectorData(withJump, vector, VectorData.VectorType.Jump));
double[] jumpValues = {startX, startY, startZ};
super.doJump(player, jumpValues);
existingVelocities.add(new VectorData(jumpValues[0], jumpValues[1], jumpValues[2], vector, VectorData.VectorType.Jump));
}
}
}
@@ -19,20 +19,24 @@ import java.util.Set;
public class PredictionEngineNormal extends PredictionEngine {
public static void staticVectorEndOfTick(GrimPlayer player, Vector3dm vector) {
double adjustedY = vector.getY();
public static double staticVectorEndOfTickY(GrimPlayer player, double y) {
double adjustedY = y;
final OptionalInt levitation = player.compensatedEntities.getPotionLevelForPlayer(PotionTypes.LEVITATION);
if (levitation.isPresent()) {
adjustedY += (0.05 * (levitation.getAsInt() + 1) - vector.getY()) * 0.2;
adjustedY += (0.05 * (levitation.getAsInt() + 1) - y) * 0.2;
// Reset fall distance with levitation
player.fallDistance = 0;
} else if (player.hasGravity) {
adjustedY -= player.gravity;
}
vector.setX(vector.getX() * player.friction);
vector.setY(adjustedY * 0.98F);
vector.setZ(vector.getZ() * player.friction);
return adjustedY * 0.98F;
}
public static void staticVectorEndOfTick(GrimPlayer player, VectorData vectorData) {
vectorData.vectorX = vectorData.vectorX * player.friction;
vectorData.vectorY = staticVectorEndOfTickY(player, vectorData.vectorY);
vectorData.vectorZ = vectorData.vectorZ * player.friction;
}
@Override
@@ -41,8 +45,9 @@ public class PredictionEngineNormal extends PredictionEngine {
return;
}
for (VectorData vector : new HashSet<>(existingVelocities)) {
Vector3dm jump = vector.vector.clone();
final VectorData[] vectorsToProcess = existingVelocities.toArray(new VectorData[0]);
for (VectorData vector : vectorsToProcess) {
double[] jumpValues = {vector.vectorX, vector.vectorY, vector.vectorZ};
if (!player.isFlying) {
// Negative jump boost does not allow the player to leave the ground
@@ -54,17 +59,17 @@ public class PredictionEngineNormal extends PredictionEngine {
if (((jumpBoost.isEmpty() || jumpBoost.getAsInt() >= 0) && player.onGround) || !player.lastOnGround)
return;
JumpPower.jumpFromGround(player, jump);
JumpPower.jumpFromGround(player, jumpValues);
} else {
jump.add(new Vector3dm(0, player.flySpeed * 3, 0));
jumpValues[1] = player.flySpeed * 3;
if (!player.wasFlying) {
Vector3dm edgeCaseJump = jump.clone();
JumpPower.jumpFromGround(player, edgeCaseJump);
existingVelocities.add(vector.returnNewModified(edgeCaseJump, VectorData.VectorType.Jump));
double[] edgeCaseJumpValues = {jumpValues[0], jumpValues[1], jumpValues[2]};
JumpPower.jumpFromGround(player, edgeCaseJumpValues);
existingVelocities.add(vector.returnNewModified(edgeCaseJumpValues[0], edgeCaseJumpValues[1], edgeCaseJumpValues[2], VectorData.VectorType.Jump));
}
}
existingVelocities.add(vector.returnNewModified(jump, VectorData.VectorType.Jump));
existingVelocities.add(vector.returnNewModified(jumpValues[0], jumpValues[1], jumpValues[2], VectorData.VectorType.Jump));
}
}
@@ -86,32 +91,30 @@ public class PredictionEngineNormal extends PredictionEngine {
if (player.lastWasClimbing == 0 && (player.pointThreeEstimator.isNearClimbable() || player.isClimbing) && (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_14)
|| !Collisions.isEmpty(player, player.boundingBox.copy().expand(
player.clientVelocity.getX(), 0, player.clientVelocity.getZ()).expand(0.5, -SimpleCollisionBox.COLLISION_EPSILON, 0.5))) || walkingOnPowderSnow) {
Vector3dm ladderVelocity = player.clientVelocity.clone().setY(0.2);
staticVectorEndOfTick(player, ladderVelocity);
player.lastWasClimbing = ladderVelocity.getY();
player.lastWasClimbing = staticVectorEndOfTickY(player, 0.2);
}
for (VectorData vector : player.getPossibleVelocitiesMinusKnockback()) {
staticVectorEndOfTick(player, vector.vector);
staticVectorEndOfTick(player, vector);
}
}
@Override
public Vector3dm handleOnClimbable(Vector3dm vector, GrimPlayer player) {
public Vector3dm handleOnClimbable(double x, double y, double z, GrimPlayer player) {
if (player.isClimbing) {
// Reset fall distance when climbing
player.fallDistance = 0;
vector.setX(GrimMath.clamp(vector.getX(), -0.15F, 0.15F));
vector.setZ(GrimMath.clamp(vector.getZ(), -0.15F, 0.15F));
vector.setY(Math.max(vector.getY(), -0.15F));
x = GrimMath.clamp(x, -0.15F, 0.15F);
z = GrimMath.clamp(z, -0.15F, 0.15F);
y = Math.max(y, -0.15F);
// Yes, this uses shifting not crouching
if (vector.getY() < 0.0 && !(player.compensatedWorld.getBlockType(player.lastX, player.lastY, player.lastZ) == StateTypes.SCAFFOLDING) && player.isSneaking && !player.isFlying) {
vector.setY(0.0);
if (y < 0.0 && !(player.compensatedWorld.getBlockType(player.lastX, player.lastY, player.lastZ) == StateTypes.SCAFFOLDING) && player.isSneaking && !player.isFlying) {
y = 0.0;
}
}
return vector;
return new Vector3dm(x, y, z);
}
}
@@ -19,12 +19,19 @@ public class PredictionEngineWater extends PredictionEngine {
private double playerGravity;
private float swimmingFriction;
public static void staticVectorEndOfTick(GrimPlayer player, Vector3dm vector, float swimmingFriction, double playerGravity, boolean isFalling) {
vector.multiply(new Vector3dm(swimmingFriction, 0.8F, swimmingFriction));
Vector3dm fluidVector = FluidFallingAdjustedMovement.getFluidFallingAdjustedMovement(player, playerGravity, isFalling, vector);
vector.setX(fluidVector.getX());
vector.setY(fluidVector.getY());
vector.setZ(fluidVector.getZ());
public static void staticVectorEndOfTick(GrimPlayer player, double[] vector, float swimmingFriction, double playerGravity, boolean isFalling) {
vector[0] *= swimmingFriction;
vector[1] *= 0.8F;
vector[2] *= swimmingFriction;
double y = FluidFallingAdjustedMovement.getAdjustedY(player, playerGravity, isFalling, vector[1]);
vector[1] = y;
}
public static void staticVectorEndOfTick(GrimPlayer player, VectorData vectorData, float swimmingFriction, double playerGravity, boolean isFalling) {
vectorData.vectorX *= swimmingFriction;
vectorData.vectorY *= 0.8F;
vectorData.vectorZ *= swimmingFriction;
vectorData.vectorY = FluidFallingAdjustedMovement.getAdjustedY(player, playerGravity, isFalling, vectorData.vectorY);
}
public static Set<VectorData> transformSwimmingVectors(GrimPlayer player, Set<VectorData> base) {
@@ -45,7 +52,7 @@ public class PredictionEngineWater extends PredictionEngine {
double scalar = lookYAmount < -0.2 ? 0.085 : 0.06;
// The player can always press jump and activate this
swimmingVelocities.add(vector.returnNewModified(new Vector3dm(vector.vector.getX(), vector.vector.getY() + ((lookYAmount - vector.vector.getY()) * scalar), vector.vector.getZ()), VectorData.VectorType.SwimmingSpace));
swimmingVelocities.add(vector.returnNewModified(new Vector3dm(vector.vectorX, vector.vectorY + ((lookYAmount - vector.vectorY) * scalar), vector.vectorZ), VectorData.VectorType.SwimmingSpace));
// This scenario will occur if the player does not press jump and the other conditions are met
// Theoretically we should check this BEFORE allowing no look, but there isn't a cheat that takes advantage of this yet
@@ -54,7 +61,7 @@ public class PredictionEngineWater extends PredictionEngine {
// If you feel adventurous, re-add the following line to eliminate this unfair disadvantage
//if (d > 0.0 && player.world.getFluidLevelAt(player.lastX, player.lastY + 1.0 - 0.1, player.lastZ) == 0) {
swimmingVelocities.add(vector.returnNewModified(vector.vector, VectorData.VectorType.SurfaceSwimming));
swimmingVelocities.add(vector.returnNewModified(vector.vectorX, vector.vectorY, vector.vectorZ, VectorData.VectorType.SurfaceSwimming));
}
return swimmingVelocities;
@@ -72,17 +79,21 @@ public class PredictionEngineWater extends PredictionEngine {
@Override
public void addJumpsToPossibilities(GrimPlayer player, Set<VectorData> existingVelocities) {
for (VectorData vector : new HashSet<>(existingVelocities)) {
final double startX = vector.vectorX;
final double startY = vector.vectorY;
final double startZ = vector.vectorZ;
if (player.couldSkipTick && vector.isZeroPointZeroThree()) {
double extraVelFromVertTickSkipUpwards = GrimMath.clamp(player.actualMovement.getY(), vector.vector.clone().getY(), vector.vector.clone().getY() + 0.05f);
existingVelocities.add(new VectorData(vector.vector.clone().setY(extraVelFromVertTickSkipUpwards), vector, VectorData.VectorType.Jump));
double extraVelFromVertTickSkipUpwards = GrimMath.clamp(player.actualMovement.getY(), startY, startY + 0.05f);
existingVelocities.add(new VectorData(startX, extraVelFromVertTickSkipUpwards, startZ, vector, VectorData.VectorType.Jump));
} else {
existingVelocities.add(new VectorData(vector.vector.clone().add(new Vector3dm(0, 0.04f, 0)), vector, VectorData.VectorType.Jump));
existingVelocities.add(new VectorData(startX, startY + 0.04f, startZ, vector, VectorData.VectorType.Jump));
}
if (player.slightlyTouchingWater && player.lastOnGround && !player.onGround) {
Vector3dm withJump = vector.vector.clone();
super.doJump(player, withJump);
existingVelocities.add(new VectorData(withJump, vector, VectorData.VectorType.Jump));
double[] jumpValues = {startX, startY, startZ};
super.doJump(player, jumpValues);
existingVelocities.add(new VectorData(jumpValues[0], jumpValues[1], jumpValues[2], vector, VectorData.VectorType.Jump));
}
}
}
@@ -92,7 +103,7 @@ public class PredictionEngineWater extends PredictionEngine {
super.endOfTick(player, playerGravity);
for (VectorData vector : player.getPossibleVelocitiesMinusKnockback()) {
staticVectorEndOfTick(player, vector.vector, swimmingFriction, playerGravity, isFalling);
staticVectorEndOfTick(player, vector, swimmingFriction, playerGravity, isFalling);
}
}
@@ -101,7 +112,7 @@ public class PredictionEngineWater extends PredictionEngine {
// "hacky" climbing where player enters ladder within 0.03 movement (WHY DOES 0.03 EXIST???)
if (player.lastWasClimbing == 0 && player.pointThreeEstimator.isNearClimbable() && (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_14) || !Collisions.isEmpty(player, player.boundingBox.copy().expand(
player.clientVelocity.getX(), 0, player.clientVelocity.getZ()).expand(0.5, -SimpleCollisionBox.COLLISION_EPSILON, 0.5)))) {
player.lastWasClimbing = FluidFallingAdjustedMovement.getFluidFallingAdjustedMovement(player, playerGravity, isFalling, player.clientVelocity.clone().setY(0.2D * 0.8F)).getY();
player.lastWasClimbing = FluidFallingAdjustedMovement.getAdjustedY(player, playerGravity, isFalling, 0.2D * 0.8F);
}
Set<VectorData> baseVelocities = super.fetchPossibleStartTickVectors(player);
@@ -20,8 +20,8 @@ public class PredictionEngineWaterLegacy extends PredictionEngine {
// This is just the vanilla equation for legacy water movement
@Override
public Vector3dm getMovementResultFromInput(GrimPlayer player, Vector3dm inputVector, float f, float f2) {
float lengthSquared = (float) inputVector.lengthSquared();
public Vector3dm getMovementResultFromInput(GrimPlayer player, double x, double y, double z, float f, float f2) {
float lengthSquared = (float) GrimMath.lengthSquared(x, y, z);
if (lengthSquared >= 1.0E-4F) {
lengthSquared = (float) Math.sqrt(lengthSquared);
@@ -31,13 +31,15 @@ public class PredictionEngineWaterLegacy extends PredictionEngine {
}
lengthSquared = swimmingSpeed / lengthSquared;
inputVector.multiply(lengthSquared);
x *= lengthSquared;
y *= lengthSquared;
z *= lengthSquared;
float yawRadians = GrimMath.radians(player.yaw);
float sinResult = player.trigHandler.sin(yawRadians);
float cosResult = player.trigHandler.cos(yawRadians);
return new Vector3dm(inputVector.getX() * cosResult - inputVector.getZ() * sinResult,
inputVector.getY(), inputVector.getZ() * cosResult + inputVector.getX() * sinResult);
return new Vector3dm(x * cosResult - z * sinResult,
y, z * cosResult + x * sinResult);
}
return new Vector3dm();
@@ -46,11 +48,12 @@ public class PredictionEngineWaterLegacy extends PredictionEngine {
@Override
public void addJumpsToPossibilities(GrimPlayer player, Set<VectorData> existingVelocities) {
for (VectorData vector : new HashSet<>(existingVelocities)) {
existingVelocities.add(new VectorData(vector.vector.clone().add(new Vector3dm(0, 0.04f, 0)), vector, VectorData.VectorType.Jump));
final VectorData[] vectorsToProcess = existingVelocities.toArray(new VectorData[0]);
for (VectorData vector : vectorsToProcess) {
existingVelocities.add(new VectorData(vector.vectorX, vector.vectorY + 0.04f, vector.vectorZ, vector, VectorData.VectorType.Jump));
if (player.skippedTickInActualMovement) {
existingVelocities.add(new VectorData(vector.vector.clone().add(new Vector3dm(0, 0.02f, 0)), vector, VectorData.VectorType.Jump));
existingVelocities.add(new VectorData(vector.vectorX,vector.vectorY + 0.02f, vector.vectorZ, vector, VectorData.VectorType.Jump));
}
}
}
@@ -60,10 +63,12 @@ public class PredictionEngineWaterLegacy extends PredictionEngine {
super.endOfTick(player, playerGravity);
for (VectorData vector : player.getPossibleVelocitiesMinusKnockback()) {
vector.vector.multiply(new Vector3dm(swimmingFriction, 0.8F, swimmingFriction));
vector.vectorX *= swimmingFriction;
vector.vectorY *= 0.8F;
vector.vectorZ *= swimmingFriction;
// Gravity
vector.vector.setY(vector.vector.getY() - 0.02D);
vector.vectorY -= 0.02D;
}
}
}
@@ -7,6 +7,7 @@ import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
import ac.grim.grimac.utils.data.VectorData;
import ac.grim.grimac.utils.enums.BoatEntityStatus;
import ac.grim.grimac.utils.math.GrimMath;
import ac.grim.grimac.utils.math.Vec2d;
import ac.grim.grimac.utils.math.Vector3dm;
import ac.grim.grimac.utils.nmsutil.BlockProperties;
import ac.grim.grimac.utils.nmsutil.Collisions;
@@ -146,7 +147,7 @@ public class PredictionEngineBoat extends PredictionEngine {
for (VectorData data : possibleVectors) {
// TODO: is this correct?
data.input = new Vector3dm(player.vehicleData.vehicleForward, 0, player.vehicleData.vehicleHorizontal);
data.input = new Vec2d(player.vehicleData.vehicleForward, player.vehicleData.vehicleHorizontal);
for (int applyStuckSpeed = 1; applyStuckSpeed >= 0; applyStuckSpeed--) {
if (applyStuckSpeed == 0 && player.isForceStuckSpeed()) break;
@@ -155,14 +156,41 @@ public class PredictionEngineBoat extends PredictionEngine {
// so if a player tries to move in both directions, a packet will
// show that the player is staying, but the boat will move anyway
if (player.vehicleData.vehicleForward == 0) {
Vector3dm vector = data.vector.clone();
controlBoat(player, vector, true);
if (applyStuckSpeed != 0) vector.multiply(player.stuckSpeedMultiplier);
vectors.add(data.returnNewModified(vector, VectorData.VectorType.InputResult));
float f = 0.035F;
double x = data.vectorX + player.trigHandler.sin(GrimMath.radians(-player.yaw)) * f;
double y = data.vectorY;
double z = data.vectorZ + (double) (player.trigHandler.cos(GrimMath.radians(player.yaw)) * f);
if (applyStuckSpeed != 0) {
x *= player.stuckSpeedMultiplier.getX();
y *= player.stuckSpeedMultiplier.getY();
z *= player.stuckSpeedMultiplier.getZ();
}
vectors.add(data.returnNewModified(x, y, z, VectorData.VectorType.InputResult));
}
controlBoat(player, data.vector, false);
if (applyStuckSpeed != 0) data.vector.multiply(player.stuckSpeedMultiplier);
float f;
if (player.vehicleData.vehicleHorizontal != 0) {
f = 0.005F;
if (player.vehicleData.vehicleForward > 0.1) {
f += 0.04F;
} else if (player.vehicleData.vehicleForward < -0.01) {
f -= 0.005F;
}
} else {
f = 0.0F;
}
// TODO (Mutation) (Confirm if Correct)
// It seems possible for applyStuckSpeed = 0, and 1 to add the same vector to the set twice
// since we're mutating data.vectorX, Y, Z which is very likely incorrect but I don't know enough to say for certain
data.vectorX = data.vectorX + player.trigHandler.sin(GrimMath.radians(-player.yaw)) * f;
data.vectorZ = data.vectorZ + (double) (player.trigHandler.cos(GrimMath.radians(player.yaw)) * f);
if (applyStuckSpeed != 0) {
data.vectorX *= player.stuckSpeedMultiplier.getX();
data.vectorY *= player.stuckSpeedMultiplier.getY();
data.vectorZ *= player.stuckSpeedMultiplier.getZ();
}
vectors.add(data);
}
}
@@ -176,7 +204,7 @@ public class PredictionEngineBoat extends PredictionEngine {
addFluidPushingToStartingVectors(player, vectors);
for (VectorData data : vectors) {
floatBoat(player, data.vector);
floatBoat(player, data);
}
return vectors;
@@ -186,6 +214,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
@@ -193,7 +225,7 @@ public class PredictionEngineBoat extends PredictionEngine {
return false;
}
private void floatBoat(GrimPlayer player, Vector3dm vector) {
private void floatBoat(GrimPlayer player, VectorData vector) {
double d1 = player.hasGravity ? -0.04f : 0;
double d2 = 0.0D;
float invFriction = 0.05F;
@@ -204,7 +236,7 @@ public class PredictionEngineBoat extends PredictionEngine {
player.lastY = getWaterLevelAbove(player) - 0.5625F + 0.101D;
player.boundingBox = GetBoundingBox.getCollisionBoxForPlayer(player, player.lastX, player.lastY, player.lastZ);
player.actualMovement = new Vector3dm(player.x - player.lastX, player.y - player.lastY, player.z - player.lastZ);
vector.setY(0);
vector.vectorY = 0;
player.vehicleData.lastYd = 0.0D;
player.vehicleData.status = BoatEntityStatus.IN_WATER;
@@ -225,12 +257,12 @@ public class PredictionEngineBoat extends PredictionEngine {
player.vehicleData.landFriction /= 2.0F;
}
vector.setX(vector.getX() * invFriction);
vector.setY(vector.getY() + d1);
vector.setZ(vector.getZ() * invFriction);
vector.vectorX *= invFriction;
vector.vectorY += d1;
vector.vectorZ *= invFriction;
if (d2 > 0.0D) {
double yVel = vector.getY();
double yVel = vector.vectorY;
vector.setY((yVel + d2 * 0.06153846016296973D) * 0.75D);
}
}
@@ -268,22 +300,4 @@ public class PredictionEngineBoat extends PredictionEngine {
return (float) (l + 1);
}
private void controlBoat(GrimPlayer player, Vector3dm vector, boolean intermediate) {
float f = 0.0F;
if (player.vehicleData.vehicleHorizontal != 0 && (!intermediate && player.vehicleData.vehicleForward == 0)) {
f += 0.005F;
}
//player.boatData.yRot += player.boatData.deltaRotation;
if (intermediate || player.vehicleData.vehicleForward > 0.1) {
f += 0.04F;
}
if (intermediate || player.vehicleData.vehicleForward < -0.01) {
f -= 0.005F;
}
vector.add(new Vector3dm(player.trigHandler.sin(GrimMath.radians(-player.yaw)) * f, 0, (double) (player.trigHandler.cos(GrimMath.radians(player.yaw)) * f)));
}
}
@@ -18,9 +18,9 @@ public class PredictionEngineHappyGhast extends PredictionEngineNormal {
@Override
public void endOfTick(GrimPlayer player, double delta) {
for (VectorData vector : player.getPossibleVelocitiesMinusKnockback()) {
vector.vector.setX(vector.vector.getX() * multiplier);
vector.vector.setY(vector.vector.getY() * multiplier);
vector.vector.setZ(vector.vector.getZ() * multiplier);
vector.vectorX *= multiplier;
vector.vectorY *= multiplier;
vector.vectorZ *= multiplier;
}
}
@@ -30,15 +30,15 @@ public class PredictionEngineHappyGhast extends PredictionEngineNormal {
}
@Override
public Vector3dm getMovementResultFromInput(GrimPlayer player, Vector3dm inputVector, float flyingSpeed, float yRot) {
public Vector3dm getMovementResultFromInput(GrimPlayer player, double x, double y, double z, float flyingSpeed, float yRot) {
float yRotRadians = GrimMath.radians(yRot);
float sin = player.trigHandler.sin(yRotRadians);
float cos = player.trigHandler.cos(yRotRadians);
double xResult = inputVector.getX() * cos - inputVector.getZ() * sin;
double zResult = inputVector.getZ() * cos + inputVector.getX() * sin;
double xResult = x * cos - z * sin;
double zResult = z * cos + x * sin;
return new Vector3dm(xResult * flyingSpeed, inputVector.getY() * flyingSpeed, zResult * flyingSpeed);
return new Vector3dm(xResult * flyingSpeed, y * flyingSpeed, zResult * flyingSpeed);
}
}
@@ -7,6 +7,7 @@ import ac.grim.grimac.utils.data.VectorData;
import ac.grim.grimac.utils.data.packetentity.PacketEntityCamel;
import ac.grim.grimac.utils.data.packetentity.PacketEntityHorse;
import ac.grim.grimac.utils.math.GrimMath;
import ac.grim.grimac.utils.math.Vec2d;
import ac.grim.grimac.utils.math.Vector3dm;
import ac.grim.grimac.utils.nmsutil.BlockProperties;
import ac.grim.grimac.utils.nmsutil.JumpPower;
@@ -59,10 +60,12 @@ public final class PredictionEngineRideableUtils {
}
final double multiplier = (double) (22.2222F * player.vehicleData.horseJump) * camel.getAttributeValue(Attributes.MOVEMENT_SPEED) * (double) BlockProperties.getBlockSpeedFactor(player, player.mainSupportingBlockData, new Vector3d(player.lastX, player.lastY, player.lastZ));
Vector3dm jumpVelocity = ReachUtils.getLook(player, player.yaw, player.pitch).multiply(new Vector3dm(1.0, 0.0, 1.0)).normalize().multiply(multiplier).add(new Vector3dm(0, (double) (1.4285F * player.vehicleData.horseJump) * jumpYVelocity, 0));
Vector3dm jumpVelocity = ReachUtils.getLook(player, player.yaw, player.pitch).multiply(1.0, 0.0, 1.0).normalize().multiply(multiplier).add(0, (double) (1.4285F * player.vehicleData.horseJump) * jumpYVelocity, 0);
for (VectorData vectorData : possibleVectors) {
vectorData.vector.add(jumpVelocity);
vectorData.vectorX += jumpVelocity.getX();
vectorData.vectorY += jumpVelocity.getY();
vectorData.vectorZ += jumpVelocity.getZ();
}
player.vehicleData.horseJumping = true;
@@ -102,9 +105,10 @@ public final class PredictionEngineRideableUtils {
float f3 = player.trigHandler.cos(yawRadians);
for (VectorData vectorData : possibleVectors) {
vectorData.vector.setY(jumpVelocity);
vectorData.vectorY = jumpVelocity;
if (forwardInput > 0.0F) {
vectorData.vector.add(new Vector3dm(-0.4F * f2 * player.vehicleData.horseJump, 0.0D, 0.4F * f3 * player.vehicleData.horseJump));
vectorData.vectorX += -0.4F * f2 * player.vehicleData.horseJump;
vectorData.vectorZ += 0.4F * f3 * player.vehicleData.horseJump;
}
}
@@ -122,22 +126,24 @@ public final class PredictionEngineRideableUtils {
for (int applyStuckSpeed = 1; applyStuckSpeed >= 0; applyStuckSpeed--) {
if (applyStuckSpeed == 0 && player.isForceStuckSpeed()) break;
VectorData result = new VectorData(possibleLastTickOutput.vector.clone().add(predictionEngine.getMovementResultFromInput(player, movementVector, speed, player.yaw)), possibleLastTickOutput, VectorData.VectorType.InputResult);
result.input = new Vector3dm(player.vehicleData.vehicleForward, 0, player.vehicleData.vehicleHorizontal);
Vector3dm vector = result.vector.clone();
Vector3dm vector3dm = predictionEngine.getMovementResultFromInput(player, movementVector.getX(), movementVector.getY(), movementVector.getZ(), speed, player.yaw).add(possibleLastTickOutput.vectorX, possibleLastTickOutput.vectorY, possibleLastTickOutput.vectorZ);
VectorData result = new VectorData(vector3dm, possibleLastTickOutput, VectorData.VectorType.InputResult);
result.input = new Vec2d(player.vehicleData.vehicleForward, player.vehicleData.vehicleHorizontal);
Vector3dm vector = new Vector3dm(result.vectorX, result.vectorY, result.vectorZ);
if (applyStuckSpeed != 0) vector.multiply(player.stuckSpeedMultiplier);
result = result.returnNewModified(vector, VectorData.VectorType.StuckMultiplier);
result = result.returnNewModified(new PredictionEngineNormal().handleOnClimbable(result.vector.clone(), player), VectorData.VectorType.Climbable);
result = result.returnNewModified(new PredictionEngineNormal().handleOnClimbable(result.vectorX, result.vectorY, result.vectorZ, player), VectorData.VectorType.Climbable);
returnVectors.add(result);
// This is the laziest way to reduce false positives such as horse rearing
// No bypasses can ever be derived from this, so why not?
result = new VectorData(possibleLastTickOutput.vector.clone(), possibleLastTickOutput, VectorData.VectorType.InputResult);
result.input = new Vector3dm(player.vehicleData.vehicleForward, 0, player.vehicleData.vehicleHorizontal);
vector = result.vector.clone();
result = new VectorData(possibleLastTickOutput.vectorX, possibleLastTickOutput.vectorY, possibleLastTickOutput.vectorZ, possibleLastTickOutput, VectorData.VectorType.InputResult);
result.input = new Vec2d(player.vehicleData.vehicleForward, player.vehicleData.vehicleHorizontal);
vector = new Vector3dm(result.vectorX, result.vectorY, result.vectorZ);
if (applyStuckSpeed != 0) vector.multiply(player.stuckSpeedMultiplier);
result = result.returnNewModified(vector, VectorData.VectorType.StuckMultiplier);
result = result.returnNewModified(new PredictionEngineNormal().handleOnClimbable(result.vector.clone(), player), VectorData.VectorType.Climbable);
result = result.returnNewModified(new PredictionEngineNormal().handleOnClimbable(result.vectorX, result.vectorY, result.vectorZ, player), VectorData.VectorType.Climbable);
returnVectors.add(result);
}
}
@@ -30,40 +30,117 @@ public class MessageUtil {
return vec == null ? "null" : vec.x + ", " + vec.y + ", " + vec.z;
}
public @NotNull String toUnlabeledString(int x, int y, int z) {
return x + ", " + y + ", " + z;
}
public @NotNull String toUnlabeledString(double x, double y, double z) {
return x + ", " + y + ", " + z;
}
public @NotNull String toUnlabledString(@Nullable Vector3f vec) {
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) {
@@ -14,7 +14,9 @@ import java.util.ArrayList;
import java.util.List;
public final class BlockBreak {
public final Vector3i position;
public final int x;
public final int y;
public final int z;
public final BlockFace face;
public final int faceId;
public final DiggingAction action;
@@ -24,9 +26,11 @@ public final class BlockBreak {
@Getter
private boolean cancelled;
public BlockBreak(GrimPlayer player, Vector3i position, BlockFace face, int faceId, DiggingAction action, int sequence, WrappedBlockState block) {
public BlockBreak(GrimPlayer player, final int x, final int y, final int z, BlockFace face, int faceId, DiggingAction action, int sequence, WrappedBlockState block) {
this.player = player;
this.position = position;
this.x = x;
this.y = y;
this.z = z;
this.face = face;
this.faceId = faceId;
this.action = action;
@@ -39,12 +43,12 @@ public final class BlockBreak {
}
public SimpleCollisionBox getCombinedBox() {
CollisionBox placedOn = HitboxData.getBlockHitbox(player, player.inventory.getHeldItem().getType().getPlacedType(), player.getClientVersion(), block, true, position.x, position.y, position.z);
CollisionBox placedOn = HitboxData.getBlockHitbox(player, player.inventory.getHeldItem().getType().getPlacedType(), player.getClientVersion(), block, true, x, y, z);
List<SimpleCollisionBox> boxes = new ArrayList<>();
placedOn.downCast(boxes);
SimpleCollisionBox combined = new SimpleCollisionBox(position.x, position.y, position.z);
SimpleCollisionBox combined = new SimpleCollisionBox(x, y, z);
for (SimpleCollisionBox box : boxes) {
double minX = Math.max(box.minX, combined.minX);
double minY = Math.max(box.minY, combined.minY);
@@ -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);
@@ -567,8 +568,10 @@ public class BlockPlace {
// This happens due to the lack of an idle packet on 1.9+ clients
// On 1.8 clients this should practically never happen
if (interpWidth - width > 0.05 || interpHeight - height > 0.05) {
Vector3d entityPos = entity.trackedServerPosition.getPos();
interpBox = GetBoundingBox.getPacketEntityBoundingBox(player, entityPos.getX(), entityPos.getY(), entityPos.getZ(), entity);
final double x = entity.trackedServerPosition.getX();
final double y = entity.trackedServerPosition.getX();
final double z = entity.trackedServerPosition.getX();
interpBox = GetBoundingBox.getPacketEntityBoundingBox(player, x, y, z, entity);
}
if (box.isIntersected(interpBox)) {
@@ -591,7 +594,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);
}
}
@@ -640,9 +643,13 @@ public class BlockPlace {
Vector3dm look = ReachUtils.getLook(player, player.yaw, player.pitch);
final double distance = player.compensatedEntities.self.getAttributeValue(Attributes.BLOCK_INTERACTION_RANGE) + 3;
Vector3dm eyePos = new Vector3dm(player.x, player.y + player.getEyeHeight(), player.z);
Vector3dm endReachPos = eyePos.clone().add(new Vector3dm(look.getX() * distance, look.getY() * distance, look.getZ() * distance));
Vector3dm intercept = ReachUtils.calculateIntercept(box, eyePos, endReachPos).first();
final double eyeX = player.x;
final double eyeY = player.y + player.getEyeHeight();
final double eyeZ = player.z;
final double endX = eyeX + look.getX() * distance;
final double endY = eyeY + look.getY() * distance;
final double endZ = eyeZ + look.getZ() * distance;
Vector3dm intercept = ReachUtils.calculateIntercept(box, eyeX, eyeY, eyeZ, endX, endY, endZ).first();
// Bring this back to relative to the block
// The player didn't even click the block... (we should force resync BEFORE we get here!)
@@ -11,7 +11,12 @@ import lombok.Setter;
@Getter
@Setter
public final class PositionUpdate {
private final Vector3d from, to;
private final double fromX;
private final double fromY;
private final double fromZ;
private final double toX;
private final double toY;
private final double toZ;
private final boolean onGround;
private final SetBackData setback;
private final TeleportData teleportData;
@@ -1,5 +1,3 @@
package ac.grim.grimac.utils.anticheat.update;
import com.github.retrooper.packetevents.util.Vector3d;
public record VehiclePositionUpdate(Vector3d from, Vector3d to, float xRot, float yRot, boolean isTeleport) {}
public record VehiclePositionUpdate(double fromX, double fromY, double fromZ, double toX, double toY, double toZ, float xRot, float yRot, boolean isTeleport) {}
@@ -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
@@ -7,15 +7,17 @@ import org.jetbrains.annotations.NotNull;
public record BlockModification(
WrappedBlockState oldBlockContents,
WrappedBlockState newBlockContents,
Vector3i location,
int x,
int y,
int z,
int tick,
Cause cause
) {
@Override
public @NotNull String toString() {
return String.format(
"BlockModification{location=%s, old=%s, new=%s, tick=%d, cause=%s}",
location, oldBlockContents, newBlockContents, tick, cause
"BlockModification{x=%d, y=%d, z=%d, old=%s, new=%s, tick=%d, cause=%s}",
x, y, z, oldBlockContents, newBlockContents, tick, cause
);
}
@@ -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) -> {
@@ -1018,6 +1018,30 @@ public enum CollisionData implements CollisionFactory {
}
}, StateTypes.DRIED_GHAST),
SHELF((player, version, data, x, y, z) -> {
if (version.isOlderThan(ClientVersion.V_1_21_9)) {
// ViaVersion replacement block (planks)
return new SimpleCollisionBox(0, 0, 0, 1, 1, 1, true);
}
return switch (data.getFacing()) {
case NORTH -> new ComplexCollisionBox(3, new HexCollisionBox(0, 12, 11, 16, 16, 13), new HexCollisionBox(0, 0, 13, 16, 16, 16), new HexCollisionBox(0, 0, 11, 16, 4, 13));
case SOUTH -> new ComplexCollisionBox(3, new HexCollisionBox(0, 12, 3, 16, 16, 5), new HexCollisionBox(0, 0, 0, 16, 16, 3), new HexCollisionBox(0, 0, 3, 16, 4, 5));
case WEST -> new ComplexCollisionBox(3, new HexCollisionBox(11, 12, 0, 13, 16, 16), new HexCollisionBox(13, 0, 0, 16, 16, 16), new HexCollisionBox(11, 0, 0, 13, 4, 16));
case EAST -> new ComplexCollisionBox(3, new HexCollisionBox(3, 12, 0, 5, 16, 16), new HexCollisionBox(0, 0, 0, 3, 16, 16), new HexCollisionBox(3, 0, 0, 5, 4, 16));
default -> throw new IllegalStateException("Unexpected value: " + data.getFacing());
};
}, BlockTags.WOODEN_SHELVES.getStates().toArray(new StateType[0])),
COPPER_GOLEM_STATUE((player, version, data, x, y, z) -> {
if (version.isOlderThan(ClientVersion.V_1_21_9)) {
// ViaVersion replacement block (copper block)
return new SimpleCollisionBox(0, 0, 0, 1, 1, 1, true);
}
return new HexCollisionBox(3, 0, 3, 13, 14, 13);
}, BlockTags.COPPER_GOLEM_STATUES.getStates().toArray(new StateType[0])),
DEFAULT(new SimpleCollisionBox(0, 0, 0, 1, 1, 1, true), StateTypes.STONE);
// This should be an array... but a hashmap will do for now...
@@ -1049,7 +1073,7 @@ public enum CollisionData implements CollisionFactory {
this.materials = mList.toArray(new StateType[0]);
}
private static CollisionBox getAmethystBox(ClientVersion version, com.github.retrooper.packetevents.protocol.world.BlockFace facing, int param_0, int param_1) {
private static CollisionBox getAmethystBox(ClientVersion version, BlockFace facing, int param_0, int param_1) {
if (version.isOlderThanOrEquals(ClientVersion.V_1_16_4))
return NoCollisionBox.INSTANCE;
@@ -1146,7 +1170,11 @@ public enum CollisionData implements CollisionFactory {
// Would pre-computing all states be worth the memory cost? I doubt it
public static CollisionData getData(StateType state) { // TODO: Find a better hack for lava and scaffolding
// What the fuck mojang, why put noCollision() and then give PITCHER_CROP collision?
return state.isSolid() || state == StateTypes.LAVA || state == StateTypes.SCAFFOLDING || state == StateTypes.PITCHER_CROP || state == StateTypes.HEAVY_CORE || state == StateTypes.PALE_MOSS_CARPET || BlockTags.WALL_HANGING_SIGNS.contains(state) ? rawLookupMap.getOrDefault(state, DEFAULT) : NO_COLLISION;
return state.isSolid() || state == StateTypes.LAVA || state == StateTypes.SCAFFOLDING
|| state == StateTypes.PITCHER_CROP || state == StateTypes.HEAVY_CORE
|| state == StateTypes.PALE_MOSS_CARPET || BlockTags.WALL_HANGING_SIGNS.contains(state)
|| BlockTags.COPPER_GOLEM_STATUES.contains(state)
? rawLookupMap.getOrDefault(state, DEFAULT) : NO_COLLISION;
}
// TODO: This is wrong if a block doesn't have any hitbox and isn't specified, light block?
@@ -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));
}
@@ -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;
@@ -53,7 +56,7 @@ public class SimpleCollisionBox implements CollisionBox {
}
public SimpleCollisionBox(Vector3i pos) {
this(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + 1, pos.getY() + 1, pos.getZ() + 1);
this(pos.getX(), pos.getY(), pos.getZ());
}
// If you want to set a full block from a point
@@ -409,16 +412,16 @@ public class SimpleCollisionBox implements CollisionBox {
// Copied from hawk lol
// I would like to point out that this is magic to me and I have not attempted to understand this code
public Vector3dm intersectsRay(Ray ray, float minDist, float maxDist) {
Vector3dm invDir = new Vector3dm(1f / ray.getDirection().getX(), 1f / ray.getDirection().getY(), 1f / ray.getDirection().getZ());
Vector3dm invDir = new Vector3dm(1f / ray.getDirX(), 1f / ray.getDirY(), 1f / ray.getDirZ());
boolean signDirX = invDir.getX() < 0;
boolean signDirY = invDir.getY() < 0;
boolean signDirZ = invDir.getZ() < 0;
double tmin = ((signDirX ? maxX : minX) - ray.getOrigin().getX()) * invDir.getX();
double tmax = ((signDirX ? minX : maxX) - ray.getOrigin().getX()) * invDir.getX();
double tymin = ((signDirY ? maxY : minY) - ray.getOrigin().getY()) * invDir.getY();
double tymax = ((signDirY ? minY : maxY) - ray.getOrigin().getY()) * invDir.getY();
double tmin = ((signDirX ? maxX : minX) - ray.getOriginX()) * invDir.getX();
double tmax = ((signDirX ? minX : maxX) - ray.getOriginX()) * invDir.getX();
double tymin = ((signDirY ? maxY : minY) - ray.getOriginY()) * invDir.getY();
double tymax = ((signDirY ? minY : maxY) - ray.getOriginY()) * invDir.getY();
if (tmin > tymax || tymin > tmax) {
return null;
@@ -427,8 +430,8 @@ public class SimpleCollisionBox implements CollisionBox {
if (tymin > tmin) tmin = tymin;
if (tymax < tmax) tmax = tymax;
double tzmin = ((signDirZ ? maxZ : minZ) - ray.getOrigin().getZ()) * invDir.getZ();
double tzmax = ((signDirZ ? minZ : maxZ) - ray.getOrigin().getZ()) * invDir.getZ();
double tzmin = ((signDirZ ? maxZ : minZ) - ray.getOriginZ()) * invDir.getZ();
double tzmax = ((signDirZ ? minZ : maxZ) - ray.getOriginZ()) * invDir.getZ();
if ((tmin > tzmax) || (tzmin > tmax)) {
return null;
@@ -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
}
}
@@ -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;
@@ -39,18 +39,17 @@ public class ReachInterpolationData {
private boolean expandNonRelative = false;
private int cancelledLerpInterpolationStepsLowBound = Integer.MAX_VALUE;
public ReachInterpolationData(GrimPlayer player, SimpleCollisionBox startingLocation, TrackedPosition position, PacketEntity entity) {
final boolean isPointNine = !player.inVehicle() && player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9);
public ReachInterpolationData(GrimPlayer player, SimpleCollisionBox startingLocation, final double x, final double y, final double z, PacketEntity entity) {
final boolean unreliableTicking = !player.inVehicle() && player.canSkipTicks();
this.startingLocation = startingLocation;
final Vector3d pos = position.getPos();
this.targetLocation = new SimpleCollisionBox(pos.x, pos.y, pos.z, pos.x, pos.y, pos.z, false);
this.targetLocation = new SimpleCollisionBox(x, y, z, x, y, z, false);
this.player = player;
this.entity = entity;
// 1.9 -> 1.8 precision loss in packets
// (ViaVersion is doing some stuff that makes this code difficult)
if (!isPointNine && PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_9)) {
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_9) && PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_9)) {
targetLocation.expand(0.03125);
}
@@ -66,7 +65,8 @@ public class ReachInterpolationData {
interpolationSteps = 1;
}
if (isPointNine) interpolationStepsHighBound = getInterpolationSteps();
// If the player doesn't tick reliably, their interpolation is anywhere between min and max steps.
if (unreliableTicking) interpolationStepsHighBound = getInterpolationSteps();
}
// While riding entities, there is no interpolation.
@@ -11,22 +11,34 @@ import lombok.ToString;
public class SetBackData {
private final TeleportData teleportData;
private final float xRot, yRot;
private final Vector3dm velocity;
private final double velocityX, velocityY, velocityZ;
private boolean hasVelocity;
private final boolean vehicle;
private boolean isComplete = false;
// TODO: Rethink when we block movements for teleports, perhaps after 10 ticks or 5 blocks?
private boolean isPlugin;
private int ticksComplete = 0;
public SetBackData(TeleportData teleportData, float xRot, float yRot, Vector3dm velocity, boolean vehicle, boolean isPlugin) {
public SetBackData(TeleportData teleportData, float xRot, float yRot, double velocityX, double velocityY, double velocityZ, boolean hasVelocity, boolean vehicle, boolean isPlugin) {
this.teleportData = teleportData;
this.xRot = xRot;
this.yRot = yRot;
this.velocity = velocity;
this.velocityX = velocityX;
this.velocityY = velocityY;
this.velocityZ = velocityZ;
this.hasVelocity = hasVelocity;
this.vehicle = vehicle;
this.isPlugin = isPlugin;
}
public SetBackData(TeleportData teleportData, float xRot, float yRot, double velocityX, double velocityY, double velocityZ, boolean vehicle, boolean isPlugin) {
this(teleportData, xRot, yRot, velocityX, velocityY, velocityZ, true, vehicle, isPlugin);
}
public SetBackData(TeleportData teleportData, float xRot, float yRot, boolean vehicle, boolean isPlugin) {
this(teleportData, xRot, yRot, 0, 0, 0, false, vehicle, isPlugin);
}
public void tick() {
if (isComplete) ticksComplete++;
}
@@ -12,9 +12,13 @@ import org.jetbrains.annotations.NotNull;
@AllArgsConstructor
@Getter
public class TeleportData {
private final Vector3d location;
private final Vector3d velocity;
private final RelativeFlag flags;
private final double locationX;
private final double locationY;
private final double locationZ;
private final double velocityX;
private final double velocityY;
private final double velocityZ;
private final @NotNull RelativeFlag flags;
@Setter
private int transaction;
@Setter
@@ -36,29 +40,27 @@ public class TeleportData {
if (!isRelativeZ()) {
vector.setZ(0);
}
}
if (velocity != null && isStupidTeleportSystem) {
} else {
// WHAT WAS MOJANG THINKING MAKING TELEPORTS A REPLACEMENT PACKET FOR EXPLOSION VELOCITY /s
if (isRelativeDeltaX()) {
vector.setX(vector.getX() + velocity.getX());
vector.setX(vector.getX() + velocityX);
} else {
vector.setX(velocity.getX());
vector.setX(velocityX);
}
if (isRelativeDeltaY()) {
vector.setY(vector.getY() + velocity.getY());
vector.setY(vector.getY() + velocityY);
} else {
vector.setY(velocity.getY());
vector.setY(velocityY);
// Is this correct? Don't know don't care.
player.lastWasClimbing = 0; // Vertical movement reset
player.canSwimHop = false; // Vertical movement reset
}
if (isRelativeDeltaZ()) {
vector.setZ(vector.getZ() + velocity.getZ());
vector.setZ(vector.getZ() + velocityZ);
} else {
vector.setZ(velocity.getZ());
vector.setZ(velocityZ);
}
}
}
@@ -11,8 +11,9 @@ public final class TrackedPosition {
private static final double LEGACY_COORDINATE_SCALE = 32.0;
private final double scale;
@Setter
private Vector3d pos = new Vector3d();
private double x;
private double y;
private double z;
public TrackedPosition() {
// this.scale = player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) ? MODERN_COORDINATE_SCALE : LEGACY_COORDINATE_SCALE;
@@ -35,23 +36,38 @@ public final class TrackedPosition {
return value / scale;
}
// Method since 1.16.
public Vector3d withDelta(long x, long y, long z) {
if (x == 0L && y == 0L && z == 0L) {
return this.pos;
}
double d = x == 0L ? this.pos.x : unpack(pack(this.pos.x, scale) + x);
double e = y == 0L ? this.pos.y : unpack(pack(this.pos.y, scale) + y);
double f = z == 0L ? this.pos.z : unpack(pack(this.pos.z, scale) + z);
return new Vector3d(d, e, f);
// Methods since 1.16.
public double withDeltaX(long x) {
if (x == 0L) return this.x;
return unpack(pack(this.x, scale) + x);
}
// In 1.16-, this was different.
public Vector3d withDeltaLegacy(double x, double y, double z) {
double d = unpackLegacy(packLegacy(this.pos.x, scale) + x);
double e = unpackLegacy(packLegacy(this.pos.y, scale) + y);
double f = unpackLegacy(packLegacy(this.pos.z, scale) + z);
return new Vector3d(d, e, f);
public double withDeltaY(long y) {
if (y == 0L) return this.y;
return unpack(pack(this.y, scale) + y);
}
public double withDeltaZ(long z) {
if (z == 0L) return this.z;
return unpack(pack(this.z, scale) + z);
}
// In 1.16-, these were different.
public double withDeltaLegacyX(double x) {
return unpackLegacy(packLegacy(this.x, scale) + x);
}
public double withDeltaLegacyY(double y) {
return unpackLegacy(packLegacy(this.y, scale) + y);
}
public double withDeltaLegacyZ(double z) {
return unpackLegacy(packLegacy(this.z, scale) + z);
}
public void setPos(double x, double y, double z) {
this.x = x;
this.y = y;
this.z = z;
}
}
@@ -1,5 +1,7 @@
package ac.grim.grimac.utils.data;
import ac.grim.grimac.utils.anticheat.MessageUtil;
import ac.grim.grimac.utils.math.Vec2d;
import ac.grim.grimac.utils.math.Vector3dm;
import lombok.Getter;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@@ -10,15 +12,33 @@ public class VectorData {
public final VectorType vectorType;
public VectorData lastVector;
public VectorData preUncertainty;
public Vector3dm vector;
public @MonotonicNonNull Vector3dm input;
public double vectorX, vectorY, vectorZ;
public @MonotonicNonNull Vec2d input;
@Getter
private boolean isKnockback, firstBreadKb, isExplosion, firstBreadExplosion, isTrident, isZeroPointZeroThree, isSwimHop, isFlipSneaking, isFlipItem, isJump, isAttackSlow = false;
// For handling replacing the type of vector it is while keeping data
public VectorData(Vector3dm vector, VectorData lastVector, VectorType vectorType) {
this.vector = vector;
this(vector.getX(), vector.getY(), vector.getZ(), lastVector, vectorType);
}
public void setX(double x) {
this.vectorX = x;
}
public void setY(double y) {
this.vectorY = y;
}
public void setZ(double z) {
this.vectorZ = z;
}
public VectorData(final double x, final double y, final double z, VectorData lastVector, VectorType vectorType) {
this.vectorX = x;
this.vectorY = y;
this.vectorZ = z;
this.lastVector = lastVector;
this.vectorType = vectorType;
@@ -42,30 +62,40 @@ public class VectorData {
}
public VectorData(Vector3dm vector, VectorType vectorType) {
this.vector = vector;
this(vector.getX(), vector.getY(), vector.getZ(), vectorType);
}
public VectorData(final double x, final double y, final double z, VectorType vectorType) {
this.vectorX = x;
this.vectorY = y;
this.vectorZ = z;
this.vectorType = vectorType;
addVectorType(vectorType);
}
public VectorData returnNewModified(VectorType type) {
return new VectorData(vector, this, type);
return new VectorData(this.vectorX, this.vectorY, this.vectorZ, this, type);
}
public VectorData returnNewModified(Vector3dm newVec, VectorType type) {
return new VectorData(newVec, this, type);
}
public VectorData returnNewModified(final double x, final double y, final double z, VectorType type) {
return new VectorData(x, y, z, this, type);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
VectorData that = (VectorData) o;
return isKnockback == that.isKnockback && firstBreadKb == that.firstBreadKb && isExplosion == that.isExplosion && firstBreadExplosion == that.firstBreadExplosion && isTrident == that.isTrident && isZeroPointZeroThree == that.isZeroPointZeroThree && isSwimHop == that.isSwimHop && isFlipSneaking == that.isFlipSneaking && isFlipItem == that.isFlipItem && isJump == that.isJump && isAttackSlow == that.isAttackSlow && vectorType == that.vectorType && Objects.equals(lastVector, that.lastVector) && Objects.equals(preUncertainty, that.preUncertainty) && Objects.equals(vector, that.vector);
return isKnockback == that.isKnockback && firstBreadKb == that.firstBreadKb && isExplosion == that.isExplosion && firstBreadExplosion == that.firstBreadExplosion && isTrident == that.isTrident && isZeroPointZeroThree == that.isZeroPointZeroThree && isSwimHop == that.isSwimHop && isFlipSneaking == that.isFlipSneaking && isFlipItem == that.isFlipItem && isJump == that.isJump && isAttackSlow == that.isAttackSlow && vectorType == that.vectorType && Objects.equals(lastVector, that.lastVector) && Objects.equals(preUncertainty, that.preUncertainty) && vectorX == that.vectorX && vectorY == that.vectorY && vectorZ == that.vectorZ;
}
@Override
public int hashCode() {
return Objects.hash(vectorType, lastVector, preUncertainty, vector, isKnockback, firstBreadKb, isExplosion, firstBreadExplosion, isTrident, isZeroPointZeroThree, isSwimHop, isFlipSneaking, isFlipItem, isJump, isAttackSlow);
return Objects.hash(vectorType, lastVector, preUncertainty, vectorX, vectorY, vectorZ, isKnockback, firstBreadKb, isExplosion, firstBreadExplosion, isTrident, isZeroPointZeroThree, isSwimHop, isFlipSneaking, isFlipItem, isJump, isAttackSlow);
}
public void addVectorType(VectorType type) {
@@ -88,7 +118,7 @@ public class VectorData {
public String toString() {
return "VectorData{" +
"pointThree=" + isZeroPointZeroThree +
", vector=" + vector +
", vector=" + MessageUtil.toUnlabeledString(vectorX, vectorY, vectorZ) +
'}';
}
@@ -20,9 +20,11 @@ public class VehicleData {
public float vehicleForward = 0f;
public boolean lastDummy = false;
public boolean wasVehicleSwitch = false;
public final ConcurrentLinkedQueue<Pair<Integer, Vector3d>> vehicleTeleports = new ConcurrentLinkedQueue<>();
public final ConcurrentLinkedQueue<VehicleTeleport> vehicleTeleports = new ConcurrentLinkedQueue<>();
public float nextHorseJump = 0;
public float horseJump = 0;
public boolean horseJumping = false;
public int camelDashCooldown;
public record VehicleTeleport(int teleportId, double x, double y, double z) {}
}
@@ -73,12 +73,14 @@ public class PacketEntity extends TypedPacketEntity {
this.uuid = uuid;
initAttributes(player);
this.trackedServerPosition = new TrackedPosition();
this.trackedServerPosition.setPos(new Vector3d(x, y, z));
this.trackedServerPosition.setPos(x, y, z);
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_9)) { // Thanks ViaVersion
trackedServerPosition.setPos(new Vector3d(((int) (x * 32)) / 32d, ((int) (y * 32)) / 32d, ((int) (z * 32)) / 32d));
trackedServerPosition.setPos(((int) (x * 32)) / 32d, ((int) (y * 32)) / 32d, ((int) (z * 32)) / 32d);
}
final Vector3d pos = trackedServerPosition.getPos();
this.newPacketLocation = new ReachInterpolationData(player, new SimpleCollisionBox(pos.x, pos.y, pos.z, pos.x, pos.y, pos.z, false), trackedServerPosition, this);
final double xPos = trackedServerPosition.getX();
final double yPos = trackedServerPosition.getY();
final double zPos = trackedServerPosition.getZ();
this.newPacketLocation = new ReachInterpolationData(player, new SimpleCollisionBox(xPos, yPos, zPos, xPos, yPos, zPos, false), xPos, yPos, zPos, this);
}
protected void trackAttribute(ValuedAttribute valuedAttribute) {
@@ -129,31 +131,38 @@ public class PacketEntity extends TypedPacketEntity {
if (relative) {
// This only matters for 1.9+ clients, but it won't hurt 1.8 clients either... align for imprecision
final double scale = trackedServerPosition.getScale();
Vector3d vec3d;
final double x;
final double y;
final double z;
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_16)) {
vec3d = trackedServerPosition.withDelta(TrackedPosition.pack(relX, scale), TrackedPosition.pack(relY, scale), TrackedPosition.pack(relZ, scale));
x = trackedServerPosition.withDeltaX(TrackedPosition.pack(relX, scale));
y = trackedServerPosition.withDeltaY(TrackedPosition.pack(relY, scale));
z = trackedServerPosition.withDeltaZ(TrackedPosition.pack(relZ, scale));
} else {
vec3d = trackedServerPosition.withDeltaLegacy(TrackedPosition.packLegacy(relX, scale), TrackedPosition.packLegacy(relY, scale), TrackedPosition.packLegacy(relZ, scale));
x = trackedServerPosition.withDeltaLegacyX(TrackedPosition.packLegacy(relX, scale));
y = trackedServerPosition.withDeltaLegacyY(TrackedPosition.packLegacy(relY, scale));
z = trackedServerPosition.withDeltaLegacyZ(TrackedPosition.packLegacy(relZ, scale));
}
trackedServerPosition.setPos(vec3d);
trackedServerPosition.setPos(x, y, z);
} else {
trackedServerPosition.setPos(new Vector3d(relX, relY, relZ));
trackedServerPosition.setPos(relX, relY, relZ);
// ViaVersion desync's here for teleports
// It simply teleports the entity with its position divided by 32... ignoring the offset this causes.
// Thanks a lot ViaVersion! Please don't fix this, or it will be a pain to support.
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_9)) {
trackedServerPosition.setPos(new Vector3d(((int) (relX * 32)) / 32d, ((int) (relY * 32)) / 32d, ((int) (relZ * 32)) / 32d));
trackedServerPosition.setPos(((int) (relX * 32)) / 32d, ((int) (relY * 32)) / 32d, ((int) (relZ * 32)) / 32d);
}
}
}
this.oldPacketLocation = newPacketLocation;
this.newPacketLocation = new ReachInterpolationData(player, oldPacketLocation.getPossibleLocationCombined(), trackedServerPosition, this);
this.newPacketLocation = new ReachInterpolationData(player, oldPacketLocation.getPossibleLocationCombined(), trackedServerPosition.getX(), trackedServerPosition.getY(), trackedServerPosition.getZ(), this);
// 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();
}
@@ -207,7 +216,7 @@ public class PacketEntity extends TypedPacketEntity {
public void setPositionRaw(GrimPlayer player, SimpleCollisionBox box) {
// I'm disappointed in you mojang. Please don't set the packet position as it desyncs it...
// But let's follow this flawed client-sided logic!
this.trackedServerPosition.setPos(new Vector3d((box.maxX - box.minX) / 2 + box.minX, box.minY, (box.maxZ - box.minZ) / 2 + box.minZ));
this.trackedServerPosition.setPos((box.maxX - box.minX) / 2 + box.minX, box.minY, (box.maxZ - box.minZ) / 2 + box.minZ);
// This disables interpolation
this.newPacketLocation = new ReachInterpolationData(player, box, this);
}
@@ -0,0 +1,8 @@
package ac.grim.grimac.utils.function;
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
@FunctionalInterface
public interface BlockStateAtPosPredicate {
boolean test(WrappedBlockState state, int x, int y, int z);
}
Loaded 100 of 138 files, more files were not shown because too many files have changed in this diff. Show more