diff --git a/api/ctjs.api b/api/ctjs.api index 224cc3ea..079d8d26 100644 --- a/api/ctjs.api +++ b/api/ctjs.api @@ -2398,7 +2398,7 @@ public final class com/chattriggers/ctjs/api/world/World { public static final fun hasPlayer (Ljava/lang/String;)Z public static final fun isLoaded ()Z public static final fun isRaining ()Z - public final fun toMC ()Lnet/minecraft/client/world/ClientWorld; + public static final fun toMC ()Lnet/minecraft/client/world/ClientWorld; } public final class com/chattriggers/ctjs/api/world/World$BorderWrapper { diff --git a/build.gradle.kts b/build.gradle.kts index 85b7744e..3fecf30f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -17,6 +17,7 @@ plugins { alias(libs.plugins.loom) alias(libs.plugins.dokka) alias(libs.plugins.validator) + alias(libs.plugins.ksp) } version = property("mod_version").toString() @@ -43,6 +44,10 @@ dependencies { modApi(libs.modmenu) modRuntimeOnly(libs.devauth) dokkaPlugin(libs.versioning) + + implementation(kotlin("stdlib-jdk8")) + implementation(project(":typing-generator")) + ksp(project(":typing-generator")) } loom { @@ -61,7 +66,8 @@ java { } apiValidation { - ignoredPackages.add("com.chattriggers.ctjs.internal") + ignoredProjects += "typing-generator" + ignoredPackages += "com.chattriggers.ctjs.internal" } tasks { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c3742793..bab17c60 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ yarn = "1.20.4+build.3" loader = "0.15.3" fabric-api = "0.92.1+1.20.4" -fabric-kotlin = "1.10.16+kotlin.1.9.21" +fabric-kotlin = "1.10.17+kotlin.1.9.22" mapping-io = "0.5.1" rhino = "96d0c07966" @@ -23,9 +23,10 @@ devauth = "1.2.0" dokka = "1.9.10" # Plugin Versions -kotlin = "1.9.21" +kotlin = "1.9.22" loom = "1.5-SNAPSHOT" validator = "0.13.2" +ksp = "1.9.22-1.0.17" [libraries] minecraft = { module = "com.mojang:minecraft", version.ref = "minecraft" } @@ -50,6 +51,9 @@ modmenu = { module = "com.terraformersmc:modmenu", version.ref = "modmenu" } devauth = { module = "me.djtheredstoner:DevAuth-fabric", version.ref = "devauth" } versioning = { module = "org.jetbrains.dokka:versioning-plugin", version.ref = "dokka" } +ksp = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" } +gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } + [bundles] fabric = ["fabric-loader", "fabric-api", "fabric-kotlin"] included = ["mapping-io", "rhino", "jackson-core", "textarea", "serialization", "koffee"] @@ -61,3 +65,5 @@ serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref loom = { id = "fabric-loom", version.ref = "loom" } dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "validator" } +ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } + diff --git a/settings.gradle.kts b/settings.gradle.kts index 2107f5f7..5b918876 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -7,3 +7,4 @@ pluginManagement { } rootProject.name = "ctjs" +include(":typing-generator") diff --git a/src/main/kotlin/com/chattriggers/ctjs/api/client/Player.kt b/src/main/kotlin/com/chattriggers/ctjs/api/client/Player.kt index 2e9276f4..a84ec220 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/api/client/Player.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/api/client/Player.kt @@ -10,7 +10,6 @@ import com.chattriggers.ctjs.api.render.Renderer import com.chattriggers.ctjs.api.world.PotionEffect import com.chattriggers.ctjs.api.world.Scoreboard import com.chattriggers.ctjs.api.world.World -import com.chattriggers.ctjs.api.world.block.Block import com.chattriggers.ctjs.api.world.block.BlockFace import com.chattriggers.ctjs.api.world.block.BlockPos import gg.essential.universal.UMath diff --git a/src/main/kotlin/com/chattriggers/ctjs/api/world/World.kt b/src/main/kotlin/com/chattriggers/ctjs/api/world/World.kt index 3f0fe4b3..6a396a8b 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/api/world/World.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/api/world/World.kt @@ -28,6 +28,7 @@ import net.minecraft.world.LightType import kotlin.math.roundToInt object World { + @JvmStatic fun toMC() = UMinecraft.getMinecraft().world @JvmField diff --git a/src/main/kotlin/com/chattriggers/ctjs/engine/Register.kt b/src/main/kotlin/com/chattriggers/ctjs/engine/Register.kt index 990a22eb..b49482fb 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/engine/Register.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/engine/Register.kt @@ -1,7 +1,6 @@ package com.chattriggers.ctjs.engine import com.chattriggers.ctjs.api.triggers.* -import com.chattriggers.ctjs.internal.listeners.ClientListener @Suppress("unused", "MemberVisibilityCanBePrivate") object Register { diff --git a/typing-generator/build.gradle.kts b/typing-generator/build.gradle.kts new file mode 100644 index 00000000..d2cbcc7c --- /dev/null +++ b/typing-generator/build.gradle.kts @@ -0,0 +1,17 @@ +buildscript { + dependencies { + classpath(libs.gradle.plugin) + } +} + +plugins { + alias(libs.plugins.kotlin) +} + +repositories { + mavenCentral() +} + +dependencies { + implementation(libs.ksp) +} diff --git a/typing-generator/src/main/kotlin/com/chattriggers/ctjs/typing/Provider.kt b/typing-generator/src/main/kotlin/com/chattriggers/ctjs/typing/Provider.kt new file mode 100644 index 00000000..f55e1cc4 --- /dev/null +++ b/typing-generator/src/main/kotlin/com/chattriggers/ctjs/typing/Provider.kt @@ -0,0 +1,531 @@ +package com.chattriggers.ctjs.typing + +import com.google.devtools.ksp.* +import com.google.devtools.ksp.processing.* +import com.google.devtools.ksp.symbol.* + +class Provider : SymbolProcessorProvider { + override fun create(environment: SymbolProcessorEnvironment) = Processor(environment) +} + +@OptIn(KspExperimental::class) +class Processor(environment: SymbolProcessorEnvironment) : SymbolProcessor { + private val logger = environment.logger + private val codeGenerator = environment.codeGenerator + private val builder = StringBuilder() + private val dependentFiles = mutableListOf() + private val classNames = mutableSetOf() + private var indent = 1 + + override fun process(resolver: Resolver): List { + // 1. Get all root classes + val rootClasses = collectRoots(resolver) + + // 2. Perform a depth-first traversal and collect all types that are within MAX_DEPTH "steps" of a root class + val classes = mutableSetOf() + rootClasses.forEach { collectAllReachableClasses(it, classes, 0) } + + // 3. Transform all classes into a package-based tree structure + val packages = transformToPackageTree(classes) + + // 4. Build the file's content + build(packages, resolver) + + // 5. The output file in generated in finish() + + return emptyList() + } + + private fun collectRoots(resolver: Resolver): Set { + val manualRootDeclarations = manualRoots + .map(resolver::getKSNameFromString) + .mapNotNull(resolver::getClassDeclarationByName) + .toSet() + + return manualRootDeclarations + resolver.getAllFiles().flatMap { + dependentFiles.add(it) + + it.declarations.filter { decl -> + val qualifier = decl.packageName.asString() + !qualifier.startsWith("com.chattriggers.ctjs.internal") && + !qualifier.startsWith("com.chattriggers.ctjs.typing") && + decl.isPublic() + } + }.filterIsInstance().toSet() + } + + private fun collectAllReachableClasses(decl: KSDeclaration, classes: MutableSet, depth: Int) { + if (depth > MAX_DEPTH || decl in classes || decl is KSTypeParameter || !decl.isPublic()) + return + + if (decl is KSTypeAlias) { + collectAllReachableClasses(decl.type.resolve().declaration, classes, depth) + return + } + + if (decl !is KSClassDeclaration) { + logger.error("Tried to collect declaration of type ${decl::class.simpleName}") + return + } + + classes.add(decl) + + decl.superTypes.forEach { collectAllReachableClasses(it.resolve().declaration, classes, depth) } + + decl.declarations.forEach { + when (it) { + is KSPropertyDeclaration -> { + if (!it.isPrivate() && !it.isJavaPackagePrivate()) + collectAllReachableClasses(it.type.resolve().declaration, classes, depth + 1) + } + is KSFunctionDeclaration -> { + if (it.isPrivate() || it.isJavaPackagePrivate()) + return@forEach + + it.parameters.forEach { parameter -> + collectAllReachableClasses(parameter.type.resolve().declaration, classes, depth + 1) + } + if (it.returnType != null) + collectAllReachableClasses(it.returnType!!.resolve().declaration, classes, depth + 1) + } + is KSClassDeclaration -> collectAllReachableClasses(it, classes, depth + 1) + else -> TODO("Handle declaration class ${decl::class.simpleName} in collectAllReachableClasses") + } + } + } + + // Note: This returns a package that represents the top-level package, and is really only used as a container for + // other packages and declarations + private fun transformToPackageTree(classes: Set): Package { + val root = Package(null, ROOT_PACKAGE) + + // Skip packages (and all classes) with invalid package names + fun getPackage(pkg: String): Package? { + return pkg.split(".").fold(root) { p, name -> + if (name in typescriptReservedWords) + return null + p.getPackage(name) + } + } + + classes.forEach { + if (it.classKind == ClassKind.ENUM_ENTRY) + return@forEach + + val pkg = getPackage(it.packageName.asString()) ?: return@forEach + val name = it.name + + require(name !in pkg.classes) + pkg.classes[name] = it + + classNames.add("${pkg.path()}.$name") + } + + return root + } + + private fun build(rootPackage: Package, resolver: Resolver) { + append(prologue) + buildPackage(rootPackage, resolver) + indent -= 1 + appendLine('}') + } + + private fun buildPackage(pkg: Package, resolver: Resolver) { + val isRoot = pkg.name == ROOT_PACKAGE + + if (!isRoot) { + appendLine("namespace ${pkg.name} {") + indent++ + } + + pkg.subpackages.values.forEach { buildPackage(it, resolver) } + pkg.classes.forEach { buildClass(it.key, it.value, resolver) } + + if (!isRoot) { + indent-- + appendLine("}") + } + } + + private fun buildClass(name: String, clazz: KSClassDeclaration, resolver: Resolver) { + // Note: We take a name parameter so that we can override the name of clazz. This is done for nested classes + + val functions = clazz.getDeclaredFunctions().filter { + it.isPublic() + }.filterNot { + it.findOverridee() != null || it.simpleName.asString().let { name -> + name in excludedMethods || name in typescriptReservedWords + } + }.toList() + + // Unlike Java, JS does not allow properties and functions to have the same name, + // so in the case that a pair does share the same name, we prefer the function + val functionNames = functions.map { it.simpleName.asString() } + + val properties = clazz.getDeclaredProperties().filter { + it.isPublic() + }.filterNot { + it.simpleName.asString() in functionNames || it.findOverridee() != null + }.toList() + + val (staticFunctions, instanceFunctions) = functions.partition { it.isStatic() } + val (staticProperties, instanceProperties) = properties.partition { it.isStatic() } + val isEnum = clazz.classKind == ClassKind.ENUM_CLASS + + val nestedClasses = clazz.declarations.filterIsInstance().filter { + it.isPublic() + }.filter { + it.classKind == ClassKind.ENUM_CLASS || it.classKind == ClassKind.CLASS + }.toList() + + // Output static object first, if necessary + if (staticProperties.isNotEmpty() || staticFunctions.isNotEmpty() || nestedClasses.isNotEmpty() || isEnum) { + appendLine("const $name: {") + indented { + if (isEnum) { + clazz.declarations + .filterIsInstance() + .filter { it.classKind == ClassKind.ENUM_ENTRY } + .forEach { + appendLine("${it.simpleName.asString()}: ${clazz.path};") + } + } + + nestedClasses.forEach { + appendLine("${it.simpleName.asString()}: typeof ${it.path};") + } + + staticProperties.forEach { buildProperty(it, resolver) } + staticFunctions.forEach { buildFunction(it, resolver, omitName = false) } + } + appendLine("}") + } + + // Then output the instance interface + appendLine(buildString { + append("interface ") + append(name) + + if (clazz.typeParameters.isNotEmpty()) { + append(clazz.typeParameters.joinToString(", ", "<", ">") { + it.name.asString() + }) + } + + val (superInterfaces, superClasses) = clazz.superTypes + .map { + var decl = it.resolve().declaration + while (decl is KSTypeAlias) + decl = decl.type.resolve().declaration + it to decl as KSClassDeclaration + } + .filter { it.first.resolve() !== resolver.builtIns.anyType } + .partition { it.second.classKind == ClassKind.INTERFACE } + + val superMembers = if (superClasses.isNotEmpty()) { + require(superClasses.size == 1) + listOf(superClasses.single()) + superInterfaces + } else superInterfaces + + if (superMembers.isNotEmpty()) { + val clause = superMembers.map { (ref, _) -> + buildType(ref, resolver).let { + if (it == "number") "kotlin.Number" else it + } + }.filter { it != "unknown" }.joinToString() + if (clause.isNotBlank()) { + append(" extends ") + append(clause.trim()) + } + } + + append(" { ") + }) + + indented { + if (clazz.classKind == ClassKind.OBJECT) { + staticProperties.forEach { buildProperty(it, resolver) } + staticFunctions.forEach { buildFunction(it, resolver, omitName = false) } + } + instanceProperties.forEach { buildProperty(it, resolver) } + instanceFunctions.forEach { buildFunction(it, resolver, omitName = false) } + + // If this is a functional interface, output a call method + if (clazz.isAnnotationPresent(FunctionalInterface::class)) { + val functionalMethod = getFunctionalInterfaceMethod(clazz) + if (functionalMethod != null) { + buildFunction(functionalMethod, resolver, omitName = true) + } + } else if (clazz.path.startsWith("kotlin.Function") && clazz.path != "kotlin.Function") { + val functionalMethod = clazz.getDeclaredFunctions().single() + buildFunction(functionalMethod, resolver, omitName = true) + } + } + + appendLine("}") + } + + private fun buildProperty(property: KSPropertyDeclaration, resolver: Resolver) { + if (property.docString != null) + append(formatDocString(property.docString!!)) + + if (property.isAnnotationPresent(JvmField::class) || (property.getter == null && property.setter == null)) { + appendLine(buildString { + append(property.simpleName.asString()) + append(": ") + append(buildType(property.type, resolver)) + append(';') + }) + } else { + val getter = property.getter + val setter = property.setter + + if (getter != null && (getter.modifiers.isEmpty() || getter.modifiers.contains(Modifier.PUBLIC))) { + appendLine(buildString { + append(resolver.getJvmName(getter)!!) + if (getter.returnType != null) { + append("(): ") + append(buildType(getter.returnType!!, resolver)) + append(';') + } else { + append("(): void;") + } + }) + } + + if (setter != null && (setter.modifiers.isEmpty() || setter.modifiers.contains(Modifier.PUBLIC))) { + appendLine(buildString { + append(resolver.getJvmName(setter)!!) + append("(value: ") + append(buildType(setter.parameter.type, resolver)) + append("): void;") + }) + } + } + } + + private fun buildFunction(function: KSFunctionDeclaration, resolver: Resolver, omitName: Boolean) { + val parameterSets = if (function.isAnnotationPresent(JvmOverloads::class)) { + // Append Int.MAX_VALUE to ensure we get an overload that contains all default parameters + val defaultIndicesToStopAt = function.parameters.mapIndexedNotNull { index, parameter -> + if (parameter.hasDefault) index else null + } + Int.MAX_VALUE + + defaultIndicesToStopAt.map { defaultIndexToStopAt -> + function.parameters.filterIndexed { index, parameter -> + !parameter.hasDefault || index < defaultIndexToStopAt + } + } + } else listOf(function.parameters) + + val functionName = if (omitName) "" else function.simpleName.asString() + + for (parameters in parameterSets) { + if (function.docString != null) + append(formatDocString(function.docString!!)) + + appendLine(buildString { + append(if (functionName == "") "new" else functionName) + + if (function.typeParameters.isNotEmpty()) + append(function.typeParameters.joinToString(", ", "<", ">") { it.name.asString() }) + + append('(') + append(parameters.joinToString { + "${it.name!!.asString().safeName()}: ${buildType(it.type, resolver)}" + }) + append(')') + + if (function.returnType != null) { + append(": ") + append(buildType(function.returnType!!, resolver)) + } + + append(';') + }) + } + } + + private fun buildType(reference: KSTypeReference, resolver: Resolver): String { + val type = reference.resolve() + (type.declaration as? KSTypeParameter)?.let { + return it.name.asString() + } + (type.declaration as? KSTypeAlias)?.let { + return buildType((type.declaration as KSTypeAlias).type, resolver) + } + + return buildString { + val builtinType = when (type) { + resolver.builtIns.anyType -> "any" + resolver.builtIns.booleanType -> "boolean" + resolver.builtIns.byteType, + resolver.builtIns.charType, + resolver.builtIns.doubleType, + resolver.builtIns.floatType, + resolver.builtIns.intType, + resolver.builtIns.longType, + resolver.builtIns.numberType, + resolver.builtIns.shortType -> "number" + resolver.builtIns.stringType -> "string" + resolver.builtIns.unitType -> "void" + resolver.builtIns.nothingType -> "never" + else -> null + } + + when { + builtinType != null -> append(builtinType) + type == resolver.getClassDeclarationByName(resolver.getKSNameFromString("org.mozilla.javascript.NativeObject")) -> + append("object") + else -> { + val path = when (val path = type.declaration.path) { + "kotlin.Any" -> "any" + "kotlin.Nothing" -> "never" + "kotlin.Unit" -> "void" + "kotlin.Byte", + "kotlin.Char", + "kotlin.Short", + "kotlin.Int", + "kotlin.Long", + "kotlin.Float", + "kotlin.Double" -> "number" + "kotlin.Boolean" -> "boolean" + "kotlin.String" -> "string" + "kotlin.collections.Set", + "kotlin.collections.MutableSet" -> "Set" + "kotlin.ByteArray", + "kotlin.CharArray", + "kotlin.ShortArray", + "kotlin.IntArray", + "kotlin.LongArray" -> return "Array" + "kotlin.Array", + "kotlin.collections.List", + "kotlin.collections.MutableList", + "kotlin.collections.Collection", + "kotlin.collections.MutableCollection" -> "Array" + "kotlin.collections.Map", + "kotlin.collections.MutableMap" -> "Map" + else -> path.takeIf { it in classNames } + } ?: return "unknown" + + append(path) + val typeArgs = reference.element?.typeArguments ?: type.arguments + if (typeArgs.isNotEmpty()) { + append(typeArgs.joinToString(", ", "<", ">") { arg -> + if (arg.variance == Variance.STAR) { + // TODO: "unknown"? + "any" + } else { + buildType(arg.type!!, resolver) + } + }) + } + } + } + + if (type.isMarkedNullable) + append(" | null | undefined") + } + } + + private fun append(s: Any) = builder.append(s) + + private fun appendLine(s: Any) { + repeat(indent) { builder.append(" ") } + builder.append(s) + builder.append('\n') + } + + private fun formatDocString(str: String) = buildString { + append("/**\n") + str.trim().lines().forEach { append(" * $it\n") } + append(" */") + }.prependIndent("\t".repeat(indent)) + "\n" + + private fun indented(block: () -> Unit) { + indent++ + block() + indent-- + check(indent >= 0) + } + + override fun finish() { + check(indent == 0) + + codeGenerator + .createNewFileByPath(Dependencies(true, *dependentFiles.toTypedArray()), "typings", "d.ts") + .write(builder.toString().toByteArray()) + } + + private fun getFunctionalInterfaceMethod(clazz: KSClassDeclaration): KSFunctionDeclaration? { + return clazz.getDeclaredFunctions().firstOrNull { it.isPublic() && it.isAbstract } + } + + private val classNameCache = mutableMapOf() + private val KSClassDeclaration.name: String + get() = classNameCache.getOrPut(this) { + val parent = parentDeclaration + if (parent is KSClassDeclaration) { + "${parent.name}$${simpleName.asString()}" + } else simpleName.asString() + } + + private val classPathCache = mutableMapOf() + private val KSDeclaration.path: String + get() = classPathCache.getOrPut(this) { + if (this is KSClassDeclaration) { + val parent = parentDeclaration + if (parent is KSClassDeclaration) { + // Omit the parent class from the path + return qualifiedName!!.getQualifier().substringBeforeLast('.') + ".$name" + } + } + + qualifiedName!!.asString() + } + + fun KSPropertyDeclaration.isStatic() = Modifier.JAVA_STATIC in modifiers || + isAnnotationPresent(JvmStatic::class) || + isAnnotationPresent(JvmField::class) + + fun KSFunctionDeclaration.isStatic() = Modifier.JAVA_STATIC in modifiers || + isAnnotationPresent(JvmStatic::class) || + isConstructor() + + private class Package(val parent: Package?, val name: String) { + val subpackages = mutableMapOf() + val classes = mutableMapOf() + + fun getPackage(name: String): Package { + return subpackages.getOrPut(name) { Package(this, name) } + } + + fun path() = generateSequence(this, Package::parent) + .toList() + .asReversed() + .drop(1) // Ignore the root package + .joinToString(".", transform = Package::name) + + override fun toString() = "Package(${path()})" + } + + companion object { + private const val MAX_DEPTH = 1000 + private const val ROOT_PACKAGE = "#root" + + private val excludedMethods = setOf( + "", "equals", "hashCode", "toString", "finalize", "compareTo", "clone", + ) + + // Typescript keywords + private val typescriptReservedWords = setOf( + "break", "case", "catch", "class", "const", "constructor", "continue", "debugger", "default", "delete", + "do", "else", "enum", "export", "extends", "false", "finally", "for", "function", "if", "import", "in", + "instanceof", "new", "null", "return", "super", "switch", "this", "throw", "true", "try", "typeof", "var", + "void", "while", "with", + ) + + private fun String.safeName() = this + if (this in typescriptReservedWords) "_" else "" + } +} diff --git a/typing-generator/src/main/kotlin/com/chattriggers/ctjs/typing/prologue.kt b/typing-generator/src/main/kotlin/com/chattriggers/ctjs/typing/prologue.kt new file mode 100644 index 00000000..ba4a9516 --- /dev/null +++ b/typing-generator/src/main/kotlin/com/chattriggers/ctjs/typing/prologue.kt @@ -0,0 +1,263 @@ +package com.chattriggers.ctjs.typing + +val manualRoots = setOf( + "java.awt.Color", + "java.util.ArrayList", + "java.util.HashMap", + "gg.essential.universal.UKeyboard", + "net.minecraft.util.Hand", + "org.lwjgl.opengl.GL11", + "org.lwjgl.opengl.GL12", + "org.lwjgl.opengl.GL13", + "org.lwjgl.opengl.GL14", + "org.lwjgl.opengl.GL15", + "org.lwjgl.opengl.GL20", + "org.lwjgl.opengl.GL21", + "org.lwjgl.opengl.GL30", + "org.lwjgl.opengl.GL31", + "org.lwjgl.opengl.GL32", + "org.lwjgl.opengl.GL33", + "org.lwjgl.opengl.GL40", + "org.lwjgl.opengl.GL41", + "org.lwjgl.opengl.GL42", + "org.lwjgl.opengl.GL43", + "org.lwjgl.opengl.GL44", + "org.lwjgl.opengl.GL45", + "org.spongepowered.asm.mixin.injection.callback.CallbackInfo", +) + +private val providedTypes = mutableMapOf( + "Keyboard" to "gg.essential.universal.UKeyboard", + "Hand" to "net.minecraft.util.Hand", + + "Client" to "com.chattriggers.ctjs.api.client.Client", + "CPS" to "com.chattriggers.ctjs.api.client.CPS", + "FileLib" to "com.chattriggers.ctjs.api.client.FileLib", + "KeyBind" to "com.chattriggers.ctjs.api.client.KeyBind", + "MathLib" to "com.chattriggers.ctjs.api.client.MathLib", + "Player" to "com.chattriggers.ctjs.api.client.Player", + "Settings" to "com.chattriggers.ctjs.api.client.Settings", + "Sound" to "com.chattriggers.ctjs.api.client.Sound", + + "Commands" to "com.chattriggers.ctjs.api.commands.DynamicCommands", + + "BlockEntity" to "com.chattriggers.ctjs.api.entity.BlockEntity", + "Entity" to "com.chattriggers.ctjs.api.entity.Entity", + "LivingEntity" to "com.chattriggers.ctjs.api.entity.LivingEntity", + "Particle" to "com.chattriggers.ctjs.api.entity.Particle", + "PlayerMP" to "com.chattriggers.ctjs.api.entity.PlayerMP", + "Team" to "com.chattriggers.ctjs.api.entity.Team", + + "Action" to "com.chattriggers.ctjs.api.inventory.action.Action", + "ClickAction" to "com.chattriggers.ctjs.api.inventory.action.ClickAction", + "DragAction" to "com.chattriggers.ctjs.api.inventory.action.DragAction", + "DropAction" to "com.chattriggers.ctjs.api.inventory.action.DropAction", + "KeyAction" to "com.chattriggers.ctjs.api.inventory.action.KeyAction", + "NBT" to "com.chattriggers.ctjs.api.inventory.nbt.NBT", + "NBTBase" to "com.chattriggers.ctjs.api.inventory.nbt.NBTBase", + "NBTTagCompound" to "com.chattriggers.ctjs.api.inventory.nbt.NBTTagCompound", + "NBTTagList" to "com.chattriggers.ctjs.api.inventory.nbt.NBTTagList", + "Inventory" to "com.chattriggers.ctjs.api.inventory.Inventory", + "Item" to "com.chattriggers.ctjs.api.inventory.Item", + "ItemType" to "com.chattriggers.ctjs.api.inventory.ItemType", + "Slot" to "com.chattriggers.ctjs.api.inventory.Slot", + + "ChatLib" to "com.chattriggers.ctjs.api.message.ChatLib", + "TextComponent" to "com.chattriggers.ctjs.api.message.TextComponent", + + "Book" to "com.chattriggers.ctjs.api.render.Book", + "Display" to "com.chattriggers.ctjs.api.render.Display", + "Gui" to "com.chattriggers.ctjs.api.render.Gui", + "Image" to "com.chattriggers.ctjs.api.render.Image", + "Rectangle" to "com.chattriggers.ctjs.api.render.Rectangle", + "Renderer" to "com.chattriggers.ctjs.api.render.Renderer", + "Renderer3d" to "com.chattriggers.ctjs.api.render.Renderer3d", + "Shape" to "com.chattriggers.ctjs.api.render.Shape", + "Text" to "com.chattriggers.ctjs.api.render.Text", + "CancellableEvent" to "com.chattriggers.ctjs.api.triggers.CancellableEvent", + + "Vec2f" to "com.chattriggers.ctjs.api.vec.Vec2f", + "Vec3f" to "com.chattriggers.ctjs.api.vec.Vec3f", + "Vec3i" to "com.chattriggers.ctjs.api.vec.Vec3i", + + "Block" to "com.chattriggers.ctjs.api.world.block.Block", + "BlockFace" to "com.chattriggers.ctjs.api.world.block.BlockFace", + "BlockPos" to "com.chattriggers.ctjs.api.world.block.BlockPos", + "BlockType" to "com.chattriggers.ctjs.api.world.block.BlockType", + "BossBars" to "com.chattriggers.ctjs.api.world.BossBars", + "Chunk" to "com.chattriggers.ctjs.api.world.Chunk", + "PotionEffect" to "com.chattriggers.ctjs.api.world.PotionEffect", + "PotionEffectType" to "com.chattriggers.ctjs.api.world.PotionEffectType", + "Scoreboard" to "com.chattriggers.ctjs.api.world.Scoreboard", + "Server" to "com.chattriggers.ctjs.api.world.Server", + "TabList" to "com.chattriggers.ctjs.api.world.TabList", + "World" to "com.chattriggers.ctjs.api.world.World", + + "Config" to "com.chattriggers.ctjs.api.Config", + + "TriggerRegister" to "com.chattriggers.ctjs.engine.Register", + "Thread" to "com.chattriggers.ctjs.engine.WrappedThread", + "Priority" to "com.chattriggers.ctjs.api.triggers.Trigger\$Priority", + "ChatTriggers" to "com.chattriggers.ctjs.CTJS", + "Console" to "com.chattriggers.ctjs.engine.Console", + + "GL11" to "org.lwjgl.opengl.GL11", + "GL12" to "org.lwjgl.opengl.GL12", + "GL13" to "org.lwjgl.opengl.GL13", + "GL14" to "org.lwjgl.opengl.GL14", + "GL15" to "org.lwjgl.opengl.GL15", + "GL20" to "org.lwjgl.opengl.GL20", + "GL21" to "org.lwjgl.opengl.GL21", + "GL30" to "org.lwjgl.opengl.GL30", + "GL31" to "org.lwjgl.opengl.GL31", + "GL32" to "org.lwjgl.opengl.GL32", + "GL33" to "org.lwjgl.opengl.GL33", + "GL40" to "org.lwjgl.opengl.GL40", + "GL41" to "org.lwjgl.opengl.GL41", + "GL42" to "org.lwjgl.opengl.GL42", + "GL43" to "org.lwjgl.opengl.GL43", + "GL44" to "org.lwjgl.opengl.GL44", + "GL45" to "org.lwjgl.opengl.GL45", +) + +val prologue = """ + /// + /// + export {}; + + declare interface String { + addFormatting(): string; + addColor(): string; + removeFormatting(): string; + replaceFormatting(): string; + } + + declare interface Number { + easeOut(to: number, speed: number, jump: number): number; + easeColor(to: number, speed: number, jump: number): java.awt.Color; + } + + interface RegisterTypes { + chat(...args: (string | unknown)[]): com.chattriggers.ctjs.api.triggers.ChatTrigger; + actionBar(...args: (string | unknown)[]): com.chattriggers.ctjs.api.triggers.ChatTrigger; + worldLoad(): com.chattriggers.ctjs.api.triggers.Trigger; + worldUnload(): com.chattriggers.ctjs.api.triggers.Trigger; + clicked(mouseX: number, mouseY: number, button: number, isPressed: boolean): com.chattriggers.ctjs.api.triggers.Trigger; + scrolled(mouseX: number, mouseY: number, scrollDelta: number): com.chattriggers.ctjs.api.triggers.Trigger; + dragged(mouseXDelta: number, mouseYDelta: number, mouseX: number, mouseY: number, mouseButton: number): com.chattriggers.ctjs.api.triggers.Trigger; + soundPlay(position: com.chattriggers.ctjs.api.vec.Vec3f, name: string, volume: number, pitch: number, category: net.minecraft.sound.SoundCategory, event: org.spongepowered.asm.mixin.injection.callback.CallbackInfo): com.chattriggers.ctjs.api.triggers.SoundPlayTrigger; + tick(ticksElapsed: number): com.chattriggers.ctjs.api.triggers.Trigger; + step(stepsElapsed: number): com.chattriggers.ctjs.api.triggers.StepTrigger; + renderWorld(partialTicks: number): com.chattriggers.ctjs.api.triggers.Trigger; + preRenderWorld(partialTicks: number): com.chattriggers.ctjs.api.triggers.Trigger; + postRenderWorld(partialTicks: number): com.chattriggers.ctjs.api.triggers.Trigger; + renderOverlay(): com.chattriggers.ctjs.api.triggers.Trigger; + renderPlayerList(event: org.spongepowered.asm.mixin.injection.callback.CallbackInfo): com.chattriggers.ctjs.api.triggers.EventTrigger; + drawBlockHighlight(position: BlockPos, event: CancellableEvent): com.chattriggers.ctjs.api.triggers.EventTrigger; + gameLoad(): com.chattriggers.ctjs.api.triggers.Trigger; + gameUnload(): com.chattriggers.ctjs.api.triggers.Trigger; + command(...args: string[]): com.chattriggers.ctjs.api.triggers.CommandTrigger; + guiOpened(screen: net.minecraft.client.gui.screen.Screen, event: org.spongepowered.asm.mixin.injection.callback.CallbackInfo): com.chattriggers.ctjs.api.triggers.EventTrigger; + guiClosed(screen: net.minecraft.client.gui.screen.Screen): com.chattriggers.ctjs.api.triggers.Trigger; + dropItem(item: Item, entireStack: boolean, event: org.spongepowered.asm.mixin.injection.callback.CallbackInfo): com.chattriggers.ctjs.api.triggers.EventTrigger; + messageSent(message: string, event: CancellableEvent): com.chattriggers.ctjs.api.triggers.EventTrigger; + itemTooltip(lore: TextComponent[], item: Item, event: org.spongepowered.asm.mixin.injection.callback.CallbackInfo): com.chattriggers.ctjs.api.triggers.EventTrigger; + playerInteract(interaction: com.chattriggers.ctjs.api.entity.PlayerInteraction, interactionTarget: Entity | Block | Item, event: CancellableEvent): com.chattriggers.ctjs.api.triggers.EventTrigger; + entityDamage(entity: Entity, attacker: PlayerMP): com.chattriggers.ctjs.api.triggers.Trigger; + entityDeath(entity: Entity): com.chattriggers.ctjs.api.triggers.Trigger; + guiRender(mouseX: number, mouseY: number, screen: net.minecraft.client.gui.screen.Screen): com.chattriggers.ctjs.api.triggers.Trigger; + guiKey(char: String, keyCode: number, screen: net.minecraft.client.gui.screen.Screen, event: CancellableEvent): com.chattriggers.ctjs.api.triggers.EventTrigger; + guiMouseClick(mouseX: number, mouseY: number, mouseButton: number, isPressed: boolean, screen: net.minecraft.client.gui.screen.Screen, event: CancellableEvent): com.chattriggers.ctjs.api.triggers.EventTrigger; + guiMouseDrag(mouseXDelta: number, mouseYDelta: number, mouseX: number, mouseY: number, mouseButton: number, screen: net.minecraft.client.gui.screen.Screen, event: CancellableEvent): com.chattriggers.ctjs.api.triggers.EventTrigger; + packetSent(packet: net.minecraft.network.packet.Packet, event: org.spongepowered.asm.mixin.injection.callback.CallbackInfo): com.chattriggers.ctjs.api.triggers.PacketTrigger; + packetReceived(packet: net.minecraft.network.packet.Packet, event: CancellableEvent): com.chattriggers.ctjs.api.triggers.PacketTrigger; + serverConnect(): com.chattriggers.ctjs.api.triggers.Trigger; + serverDisconnect(): com.chattriggers.ctjs.api.triggers.Trigger; + renderEntity(entity: Entity, partialTicks: number, event: CancellableEvent): com.chattriggers.ctjs.api.triggers.RenderEntityTrigger; + renderBlockEntity(blockEntity: BlockEntity, partialTicks: number, event: CancellableEvent): com.chattriggers.ctjs.api.triggers.RenderBlockEntityTrigger; + postGuiRender(mouseX: number, mouseY: number, screen: net.minecraft.client.gui.screen.Screen): com.chattriggers.ctjs.api.triggers.Trigger; + spawnParticle(particle: Particle, event: org.spongepowered.asm.mixin.injection.callback.CallbackInfo): com.chattriggers.ctjs.api.triggers.EventTrigger; + } + + declare global { + const Java: { + /** + * Returns the Java Class or Package given by name. If you want to + * enforce the name is a class, use Java.class() instead. + */ + type(name: string): java.lang.Package | java.lang.Class; + + /** + * Returns the Java Class given by `className`. Throws an error if the + * name is not a valid class name. + */ + class(className: string): java.lang.Class; + }; + + /** + * Runs `func` in a Java synchronized() block with `lock` as the synchronizer + */ + function sync(func: () => void, lock: unknown): void; + + /** + * Runs `func` after `delayInMs` milliseconds. A new thread is spawned to accomplish + * this, which means this function is asynchronous. If you want to avoid the Thread + * instantiation, use `Client.scheduleTask(delayInTicks, func)`. + */ + function setTimeout(func: () => void, delayInMs: number): void; + + const ArrayList: typeof java.util.ArrayList; + interface ArrayList extends java.util.ArrayList {} + const HashMap: typeof java.util.HashMap; + interface HashMap extends java.util.HashMap {} + +${providedTypes.entries.joinToString("") { (name, type) -> +"const $name: typeof $type;\ninterface $name extends $type {}\n" +}.prependIndent(" ")} + + /** + * Registers a new trigger and returns it. + */ + function register( + name: T, + cb: (...args: Parameters) => void, + ): ReturnType; + + /** + * Cancels the given event + */ + function cancel(event: CancellableEvent | org.spongepowered.asm.mixin.injection.callback.CallbackInfo): void; + + /** + * Creates a custom trigger. `name` can be used as the first argument of a + * subsequent call to `register`. Returns an object that can be used to + * invoke the trigger. + */ + function createCustomTrigger(name: string): { trigger(...args: unknown[]) }; + + function easeOut(start: number, finish: number, speed: number, jump?: number): number; + function easeColor(start: number, finish: number, speed: number, jump?: number): java.awt.Color; + + function print(message: string, color?: java.awt.Color): void; + function println(message: string, color?: java.awt.Color, end?: string): void; + + const console: { + assert(condition: boolean, message: string): void; + clear(): void; + count(label?: string): void; + debug(args: unknown[]): void; + dir(obj: object): void; + dirxml(obj: object): void; + error(...args: unknown[]): void; + group(...args: unknown[]): void; + groupCollapsed(...args: unknown[]): void; + groupEnd(...args: unknown[]): void; + info(...args: unknown[]): void; + log(...args: unknown[]): void; + table(data: object, columns?: string[]): void; + time(label?: string): void; + timeEnd(label?: string): void; + trace(...args: unknown[]): void; + warn(...args: unknown[]): void; + }; +""".trimIndent() diff --git a/typing-generator/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider b/typing-generator/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider new file mode 100644 index 00000000..ce45e0d6 --- /dev/null +++ b/typing-generator/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider @@ -0,0 +1 @@ +com.chattriggers.ctjs.typing.Provider