diff --git a/engine/engine-loader/src/main/kotlin/com/typewritermc/loader/ExtensionLoader.kt b/engine/engine-loader/src/main/kotlin/com/typewritermc/loader/ExtensionLoader.kt index 6a14daaf54..2b409c8ec6 100644 --- a/engine/engine-loader/src/main/kotlin/com/typewritermc/loader/ExtensionLoader.kt +++ b/engine/engine-loader/src/main/kotlin/com/typewritermc/loader/ExtensionLoader.kt @@ -55,37 +55,60 @@ class ExtensionLoader : KoinComponent { // TODO: Remove this when the database update is implemented extensionJson = JsonArray() - extensions = extensionJsonTexts.map { extensionJson -> - gson.fromJson(extensionJson, Extension::class.java) to extensionJson - }.filter { (extension, _) -> - if (extension.extension.engineVersion != version) { - logger.warning("Extension '${extension.extension.name}Extension' was made for Typewriter $version but you are using Typewriter ${extension.extension.engineVersion}. Ignoring extension.") - return@filter false - } + val possibleExtensions = + + extensionJsonTexts.map { extensionJson -> + gson.fromJson(extensionJson, Extension::class.java) to extensionJson + }.filter { (extension, _) -> + if (extension.extension.engineVersion != version) { + logger.warning("Extension '${extension.extension.name}Extension' was made for Typewriter $version but you are using Typewriter ${extension.extension.engineVersion}. Ignoring extension.") + return@filter false + } - val paper = extension.extension.paper - if (paper == null) { - logger.warning("Extension '${extension.extension.name}Extension' does not seem to be a paper extension. Ignoring extension.") - return@filter false - } + val paper = extension.extension.paper + if (paper == null) { + logger.warning("Extension '${extension.extension.name}Extension' does not seem to be a paper extension. Ignoring extension.") + return@filter false + } - val dependencies = paper.dependencies - val missingDependencies = dependencies.filter { !dependencyChecker.hasDependency(it) } - if (missingDependencies.isNotEmpty()) { - val missing = missingDependencies.joinToString(", ") - logger.warning( - "Extension '${extension.extension.name}Extension' is missing dependencies: '${missing}'. Ignoring extension." - ) - return@filter false + val dependencies = paper.dependencies + val missingDependencies = dependencies.filter { !dependencyChecker.hasDependency(it) } + if (missingDependencies.isNotEmpty()) { + val missing = missingDependencies.joinToString(", ") + logger.warning( + "Extension '${extension.extension.name}Extension' is missing external dependencies: '${missing}'. Ignoring extension." + ) + return@filter false + } + + true } - true - }.map { (extension, extensionJson) -> - // TODO: Remove this when the database update is implemented - this.extensionJson.add(JsonParser.parseString(extensionJson)) + extensions = possibleExtensions + .filter { (extension, _) -> + // Check if the extension dependencies are met + val missingDependencies = + extension.extension.dependencies.filter { (namespace, name) -> + possibleExtensions.none { (extension, _) -> + extension.extension.namespace == namespace && extension.extension.name == name + } + } + + if (missingDependencies.isNotEmpty()) { + val missing = missingDependencies.joinToString(", ") { "${it.namespace}:${it.name}Extension" } + logger.warning( + "Extension '${extension.extension.name}Extension' is missing extension dependencies: '${missing}'. Ignoring extension." + ) + return@filter false + } + true + } + .map { (extension, extensionJson) -> + // TODO: Remove this when the database update is implemented + this.extensionJson.add(JsonParser.parseString(extensionJson)) - extension - } + extension + } if (hasShownLoadedMessage) return hasShownLoadedMessage = true @@ -170,10 +193,17 @@ data class ExtensionInfo( val description: String = "", val version: String = "", val engineVersion: String = "", + val namespace: String = "", val flags: List = emptyList(), + val dependencies: List = emptyList(), val paper: PaperExtensionInfo? = null, ) +data class ExtensionDependencyInfo( + val namespace: String = "", + val name: String = "", +) + data class PaperExtensionInfo( val dependencies: List = emptyList(), ) diff --git a/engine/engine-paper/build.gradle.kts b/engine/engine-paper/build.gradle.kts index eb314e19d4..2d224a8e9c 100644 --- a/engine/engine-paper/build.gradle.kts +++ b/engine/engine-paper/build.gradle.kts @@ -56,8 +56,8 @@ dependencies { compileOnlyApi("net.kyori:adventure-text-serializer-legacy:$adventureVersion") compileOnlyApi("net.kyori:adventure-text-serializer-gson:$adventureVersion") - compileOnlyApi("com.github.retrooper:packetevents-api:2.7.0-SNAPSHOT") - compileOnlyApi("com.github.retrooper:packetevents-spigot:2.7.0-SNAPSHOT") + compileOnlyApi("com.github.retrooper:packetevents-api:2.7.0") + compileOnlyApi("com.github.retrooper:packetevents-spigot:2.7.0") compileOnly("me.clip:placeholderapi:2.11.6") compileOnlyApi("org.geysermc.floodgate:api:2.2.3-SNAPSHOT") diff --git a/extensions/EntityExtension/build.gradle.kts b/extensions/EntityExtension/build.gradle.kts index fa501e09cb..f908b9d7f0 100644 --- a/extensions/EntityExtension/build.gradle.kts +++ b/extensions/EntityExtension/build.gradle.kts @@ -22,6 +22,11 @@ typewriter { engineVersion = file("../../version.txt").readText().trim().substringBefore("-beta") channel = com.typewritermc.moduleplugin.ReleaseChannel.NONE + dependencies { + dependency("typewritermc", "RoadNetwork") + dependency("typewritermc", "Quest") + } + paper() } } \ No newline at end of file diff --git a/extensions/QuestExtension/build.gradle.kts b/extensions/QuestExtension/build.gradle.kts index ae13dcf8b6..31787c8745 100644 --- a/extensions/QuestExtension/build.gradle.kts +++ b/extensions/QuestExtension/build.gradle.kts @@ -19,6 +19,9 @@ typewriter { engineVersion = file("../../version.txt").readText().trim().substringBefore("-beta") channel = com.typewritermc.moduleplugin.ReleaseChannel.NONE + dependencies { + dependency("typewritermc", "RoadNetwork") + } paper() } diff --git a/extensions/VaultExtension/build.gradle.kts b/extensions/VaultExtension/build.gradle.kts index 32018656af..17fb30b7ed 100644 --- a/extensions/VaultExtension/build.gradle.kts +++ b/extensions/VaultExtension/build.gradle.kts @@ -1,5 +1,14 @@ repositories { - maven("https://jitpack.io") + exclusiveContent { + forRepository { + maven { + url = uri("https://jitpack.io") + } + } + filter { + includeGroup("com.github.MilkBowl") + } + } } dependencies { compileOnly("com.github.MilkBowl:VaultAPI:1.7.1") diff --git a/module-plugin/api/src/main/kotlin/com/typewritermc/moduleplugin/TypewriterModuleConfiguration.kt b/module-plugin/api/src/main/kotlin/com/typewritermc/moduleplugin/TypewriterModuleConfiguration.kt index 37d6c9cf14..65934f7390 100644 --- a/module-plugin/api/src/main/kotlin/com/typewritermc/moduleplugin/TypewriterModuleConfiguration.kt +++ b/module-plugin/api/src/main/kotlin/com/typewritermc/moduleplugin/TypewriterModuleConfiguration.kt @@ -1,6 +1,7 @@ package com.typewritermc.moduleplugin import com.typewritermc.loader.ExtensionFlag +import kotlinx.serialization.MissingFieldException import kotlinx.serialization.Serializable @Serializable @@ -34,16 +35,21 @@ open class TypewriterModuleConfiguration { const val MIN_NAMESPACE_LENGTH = 5 const val MAX_NAMESPACE_LENGTH = 20 -fun TypewriterModuleConfiguration.validate() { - if (namespace.isBlank()) { - throw MissingFieldException("namespace") +val NAMESPACE_REGEX = Regex("^([a-z0-9])+\$") +private fun String.validateAsNamespace(path: String) { + if (isBlank()) { + throw MissingFieldException(path) } - if (!Regex("^([a-z0-9])+\$").matches(namespace)) { + if (!NAMESPACE_REGEX.matches(this)) { throw IllegalArgumentException("Namespace must only contain lowercase letters and numbers.") } - if (namespace.length !in MIN_NAMESPACE_LENGTH..MAX_NAMESPACE_LENGTH) { - throw FieldOutsideRangeException("namespace", MIN_NAMESPACE_LENGTH, MAX_NAMESPACE_LENGTH) + if (length !in MIN_NAMESPACE_LENGTH..MAX_NAMESPACE_LENGTH) { + throw FieldOutsideRangeException(path, MIN_NAMESPACE_LENGTH, MAX_NAMESPACE_LENGTH) } +} + +fun TypewriterModuleConfiguration.validate() { + namespace.validateAsNamespace("namespace") if (engine != null && extension != null) { throw IllegalArgumentException("Cannot have both engine and extension set for the same module.") @@ -89,11 +95,18 @@ data class TypewriterExtensionConfiguration( * The release channel to use for the engine and other dependencies. */ var channel: ReleaseChannel = ReleaseChannel.RELEASE, + /** + * Dependencies on other extensions. + */ + var dependencies: ExtensionDependencyConfiguration? = null, /** * The paper extension configuration. */ var paper: PaperExtensionConfiguration? = null ) { + fun dependencies(block: ExtensionDependencyConfiguration.() -> Unit) { + dependencies = ExtensionDependencyConfiguration().apply(block) + } fun paper(block: PaperExtensionConfiguration.() -> Unit) { paper = PaperExtensionConfiguration().apply(block) } @@ -109,23 +122,28 @@ private const val MIN_SHORT_DESCRIPTION_LENGTH = 10 private const val MAX_SHORT_DESCRIPTION_LENGTH = 80 private const val MIN_DESCRIPTION_LENGTH = 100 private const val MAX_DESCRIPTION_LENGTH = 2000 +private val NAME_REGEX = Regex("^([a-zA-Z0-9])+\$") private val FORBIDDEN_WORDS = listOf("Adapter", "Extension") -internal fun TypewriterExtensionConfiguration.validate() { - if (name.isBlank()) { - throw MissingFieldException("extension.name") +private fun String.validateAsExtensionName(path: String) { + if (isBlank()) { + throw MissingFieldException(path) } - if (name.length !in MIN_NAME_LENGTH..MAX_NAME_LENGTH) { - throw FieldOutsideRangeException("extension.name", MIN_NAME_LENGTH, MAX_NAME_LENGTH) + if (length !in MIN_NAME_LENGTH..MAX_NAME_LENGTH) { + throw FieldOutsideRangeException(path, MIN_NAME_LENGTH, MAX_NAME_LENGTH) } - if (!Regex("^[a-zA-Z0-9]+$").matches(name)) { - throw IllegalArgumentException("Extension name '$name' must be alphanumeric.") + if (!NAME_REGEX.matches(this)) { + throw IllegalArgumentException("Extension name '$this' must be alphanumeric.") } for (word in FORBIDDEN_WORDS) { - if (name.contains(word, ignoreCase = true)) { - throw IllegalArgumentException("Extension name '$name' cannot contain '$word'") + if (this.contains(word, ignoreCase = true)) { + throw IllegalArgumentException("Extension name '$this' cannot contain '$word'") } } +} + +internal fun TypewriterExtensionConfiguration.validate() { + name.validateAsExtensionName("extension.name") if (shortDescription.isBlank()) { throw MissingFieldException("extension.shortDescription") @@ -152,9 +170,34 @@ internal fun TypewriterExtensionConfiguration.validate() { throw IllegalArgumentException("Version must follow the Semantic Versioning format.") } + dependencies?.validate() paper?.validate() } +@Serializable +data class ExtensionDependencyConfiguration( + val dependencies: MutableList = mutableListOf(), +) { + fun dependency(namespace: String, name: String) { + dependencies.add(ExtensionDependency(namespace, name)) + } +} + +@Serializable +data class ExtensionDependency( + val namespace: String, + val name: String, +) + +private fun ExtensionDependencyConfiguration.validate() { + dependencies.forEach { it.validate() } +} + +private fun ExtensionDependency.validate() { + namespace.validateAsNamespace("extension.dependencies.namespace") + name.validateAsExtensionName("extension.dependencies.name") +} + @Serializable data class PaperExtensionConfiguration( val dependencies: MutableList = mutableListOf(), diff --git a/module-plugin/extension-processor/src/main/kotlin/com/typewritermc/processors/ExtensionProcessor.kt b/module-plugin/extension-processor/src/main/kotlin/com/typewritermc/processors/ExtensionProcessor.kt index 5ccc06bade..44ac83a2b3 100644 --- a/module-plugin/extension-processor/src/main/kotlin/com/typewritermc/processors/ExtensionProcessor.kt +++ b/module-plugin/extension-processor/src/main/kotlin/com/typewritermc/processors/ExtensionProcessor.kt @@ -36,6 +36,10 @@ class ExtensionProcessor( "engineVersion" to JsonPrimitive(extension.engineVersion), "pluginVersion" to JsonPrimitive(pluginVersion), "version" to JsonPrimitive(version), + "namespace" to JsonPrimitive(configuration.namespace), + "dependencies" to JsonArray( + extension.dependencies?.dependencies?.map { Json.encodeToJsonElement(it) } ?: emptyList() + ) ) ) ) diff --git a/module-plugin/src/main/kotlin/com/typewritermc/TypewriterModulePlugin.kt b/module-plugin/src/main/kotlin/com/typewritermc/TypewriterModulePlugin.kt index 254e5b9676..e02cad5d3c 100644 --- a/module-plugin/src/main/kotlin/com/typewritermc/TypewriterModulePlugin.kt +++ b/module-plugin/src/main/kotlin/com/typewritermc/TypewriterModulePlugin.kt @@ -48,7 +48,7 @@ class TypewriterModulePlugin : Plugin { // Add PacketEvents repository repositories.maven { - it.setUrl("https://repo.codemc.io/repository/maven-snapshots/") + it.setUrl("https://repo.codemc.io/repository/maven-releases/") } // Add EntityLib repository repositories.maven {