diff --git a/.editorconfig b/.editorconfig
index e8a57bf1e..66b889a6a 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,6 +1,10 @@
+root = true
+
[*.{kt,kts}]
-max_line_length=120
-ij_kotlin_imports_layout=*
+indent_size = 4
+indent_style = space
+max_line_length = 120
+ij_kotlin_imports_layout = *
ij_kotlin_name_count_to_use_star_import = 2147483647
ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
insert_final_newline = true
diff --git a/src/main/kotlin/creator/custom/TemplateDescriptor.kt b/src/main/kotlin/creator/custom/TemplateDescriptor.kt
index 54890a8e3..5b6352cce 100644
--- a/src/main/kotlin/creator/custom/TemplateDescriptor.kt
+++ b/src/main/kotlin/creator/custom/TemplateDescriptor.kt
@@ -40,7 +40,7 @@ data class TemplateDescriptor(
companion object {
- const val FORMAT_VERSION = 1
+ const val FORMAT_VERSION = 2
}
}
diff --git a/src/main/kotlin/creator/custom/derivation/ExtractPaperApiVersionPropertyDerivation.kt b/src/main/kotlin/creator/custom/derivation/ExtractPaperApiVersionPropertyDerivation.kt
new file mode 100644
index 000000000..de2c57940
--- /dev/null
+++ b/src/main/kotlin/creator/custom/derivation/ExtractPaperApiVersionPropertyDerivation.kt
@@ -0,0 +1,60 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2025 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.demonwav.mcdev.creator.custom.derivation
+
+import com.demonwav.mcdev.creator.custom.PropertyDerivation
+import com.demonwav.mcdev.creator.custom.TemplateValidationReporter
+import com.demonwav.mcdev.creator.custom.types.CreatorProperty
+import com.demonwav.mcdev.util.MinecraftVersions
+import com.demonwav.mcdev.util.SemanticVersion
+
+class ExtractPaperApiVersionPropertyDerivation : ExtractVersionMajorMinorPropertyDerivation() {
+
+ override fun derive(parentValues: List): Any? {
+ val from = parentValues[0] as SemanticVersion
+ if (from >= MinecraftVersions.MC1_20_5) {
+ return from
+ }
+
+ return super.derive(parentValues);
+ }
+
+ companion object : PropertyDerivationFactory {
+
+ override fun create(
+ reporter: TemplateValidationReporter,
+ parents: List?>?,
+ derivation: PropertyDerivation
+ ): PreparedDerivation? {
+ if (parents.isNullOrEmpty()) {
+ reporter.error("Expected a parent")
+ return null
+ }
+
+ if (!parents[0]!!.acceptsType(SemanticVersion::class.java)) {
+ reporter.error("First parent must produce a semantic version")
+ return null
+ }
+
+ return ExtractPaperApiVersionPropertyDerivation()
+ }
+ }
+}
diff --git a/src/main/kotlin/creator/custom/derivation/ExtractVersionMajorMinorPropertyDerivation.kt b/src/main/kotlin/creator/custom/derivation/ExtractVersionMajorMinorPropertyDerivation.kt
index 6d5c3ac04..c8ef54b87 100644
--- a/src/main/kotlin/creator/custom/derivation/ExtractVersionMajorMinorPropertyDerivation.kt
+++ b/src/main/kotlin/creator/custom/derivation/ExtractVersionMajorMinorPropertyDerivation.kt
@@ -25,7 +25,7 @@ import com.demonwav.mcdev.creator.custom.TemplateValidationReporter
import com.demonwav.mcdev.creator.custom.types.CreatorProperty
import com.demonwav.mcdev.util.SemanticVersion
-class ExtractVersionMajorMinorPropertyDerivation : PreparedDerivation {
+open class ExtractVersionMajorMinorPropertyDerivation : PreparedDerivation {
override fun derive(parentValues: List): Any? {
val from = parentValues[0] as SemanticVersion
diff --git a/src/main/kotlin/creator/custom/model/StringList.kt b/src/main/kotlin/creator/custom/model/StringList.kt
index 95ec00df2..9256b5b84 100644
--- a/src/main/kotlin/creator/custom/model/StringList.kt
+++ b/src/main/kotlin/creator/custom/model/StringList.kt
@@ -28,4 +28,7 @@ data class StringList(val values: List) : List by values {
@JvmOverloads
fun toString(separator: String, prefix: String = "", postfix: String = ""): String =
values.joinToString(separator, prefix, postfix)
+
+ fun toStringQuoted(): String =
+ values.joinToString(", ", transform = { '"' + it + '"' })
}
diff --git a/src/main/kotlin/creator/custom/types/MavenArtifactVersionCreatorProperty.kt b/src/main/kotlin/creator/custom/types/MavenArtifactVersionCreatorProperty.kt
index fcd9011ae..e0c59d2c0 100644
--- a/src/main/kotlin/creator/custom/types/MavenArtifactVersionCreatorProperty.kt
+++ b/src/main/kotlin/creator/custom/types/MavenArtifactVersionCreatorProperty.kt
@@ -54,7 +54,7 @@ class MavenArtifactVersionCreatorProperty(
var versionFilter: (SemanticVersion) -> Boolean = { true }
override val graphProperty: GraphProperty = graph.property(SemanticVersion(emptyList()))
- private val versionsProperty = graph.property>(emptyList())
+ private val versionsProperty = graph.property>(emptySet())
private val loadingVersionsProperty = graph.property(true)
private val loadingVersionsStatusProperty = graph.property("")
@@ -127,7 +127,7 @@ class MavenArtifactVersionCreatorProperty(
descriptor.limit ?: 50
) { result ->
result.onSuccess { versions ->
- versionsProperty.set(versions)
+ versionsProperty.set(versions.toSet())
loadingVersionsProperty.set(false)
}.onFailure { exception ->
loadingVersionsStatusProperty.set(exception.message ?: exception.javaClass.simpleName)
diff --git a/src/main/kotlin/creator/custom/types/PaperVersionCreatorProperty.kt b/src/main/kotlin/creator/custom/types/PaperVersionCreatorProperty.kt
new file mode 100644
index 000000000..90866490d
--- /dev/null
+++ b/src/main/kotlin/creator/custom/types/PaperVersionCreatorProperty.kt
@@ -0,0 +1,129 @@
+/*
+ * Minecraft Development for IntelliJ
+ *
+ * https://mcdev.io/
+ *
+ * Copyright (C) 2025 minecraft-dev
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, version 3.0 only.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.demonwav.mcdev.creator.custom.types
+
+import com.demonwav.mcdev.creator.custom.CreatorContext
+import com.demonwav.mcdev.creator.custom.TemplatePropertyDescriptor
+import com.demonwav.mcdev.creator.custom.TemplateValidationReporter
+import com.demonwav.mcdev.update.PluginUtil
+import com.demonwav.mcdev.util.MinecraftVersions
+import com.demonwav.mcdev.util.SemanticVersion
+import com.intellij.ui.ComboboxSpeedSearch
+import com.intellij.ui.JBColor
+import com.intellij.ui.dsl.builder.Panel
+import com.intellij.ui.dsl.builder.bindItem
+import com.intellij.ui.dsl.builder.bindText
+import com.intellij.util.ui.AsyncProcessIcon
+import io.ktor.client.*
+import io.ktor.client.request.*
+import io.ktor.client.statement.*
+import io.ktor.http.*
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.jsonArray
+import kotlinx.serialization.json.jsonObject
+import kotlinx.serialization.json.jsonPrimitive
+
+open class PaperVersionCreatorProperty(
+ descriptor: TemplatePropertyDescriptor,
+ context: CreatorContext
+) : SemanticVersionCreatorProperty(descriptor, context) {
+
+ @OptIn(DelicateCoroutinesApi::class)
+ companion object {
+ private var paperVersions: List? = null
+
+ suspend fun getPaperVersions(): List {
+ paperVersions?.let { return it }
+
+ val client = HttpClient()
+ val response = client.get("https://fill.papermc.io/v3/projects/paper", block = {
+ this.header("User-Agent", "minecraft-dev/${PluginUtil.pluginVersion} (https://github.com/minecraft-dev/MinecraftDev)")
+ })
+ if (response.status.isSuccess()) {
+ val element = Json.parseToJsonElement(response.bodyAsText())
+ val result = element.jsonObject["versions"]?.jsonObject?.values
+ ?.asSequence()
+ ?.flatMap { it.jsonArray }
+ ?.map { it.jsonPrimitive.content }
+ ?.mapNotNull { SemanticVersion.tryParse(it) }
+ // only release versions
+ ?.filter { ver -> ver.parts.all { it is SemanticVersion.Companion.VersionPart.ReleasePart } }
+ // nothing lower than 1.18.2 should be selectable
+ ?.filter { it >= MinecraftVersions.MC1_18_2 }
+ ?.toList()
+ ?.sortedDescending()
+
+ if (result != null) {
+ paperVersions = result
+ return result
+ }
+ }
+
+ return emptyList()
+ }
+ }
+
+ private val versionsProperty = graph.property>(emptySet())
+ private val loadingVersionsProperty = graph.property(true)
+ private val loadingVersionsStatusProperty = graph.property("")
+
+ override fun buildUi(panel: Panel) {
+ panel.row(descriptor.translatedLabel) {
+ val combobox = comboBox(versionsProperty.get())
+ .bindItem(graphProperty)
+ .enabled(descriptor.editable != false)
+ .also { ComboboxSpeedSearch.installOn(it.component) }
+
+ cell(AsyncProcessIcon(makeStorageKey("progress")))
+ .visibleIf(loadingVersionsProperty)
+ label("").applyToComponent { foreground = JBColor.RED }
+ .bindText(loadingVersionsStatusProperty)
+ .visibleIf(loadingVersionsProperty)
+
+ versionsProperty.afterChange { versions ->
+ combobox.component.removeAllItems()
+ for (version in versions) {
+ combobox.component.addItem(version)
+ }
+ }
+ }.propertyVisibility()
+ }
+
+ override fun setupProperty(reporter: TemplateValidationReporter) {
+ super.setupProperty(reporter)
+ val scope = context.childScope("PaperVersionCreatorProperty")
+ scope.launch(Dispatchers.Default) {
+ val result = getPaperVersions()
+ versionsProperty.set(result.toSet())
+ loadingVersionsProperty.set(false)
+ }
+ }
+
+ class Factory : CreatorPropertyFactory {
+ override fun create(
+ descriptor: TemplatePropertyDescriptor,
+ context: CreatorContext
+ ): CreatorProperty<*> = PaperVersionCreatorProperty(descriptor, context)
+ }
+}
diff --git a/src/main/kotlin/creator/custom/types/SemanticVersionCreatorProperty.kt b/src/main/kotlin/creator/custom/types/SemanticVersionCreatorProperty.kt
index 46dae66ef..2bf327a38 100644
--- a/src/main/kotlin/creator/custom/types/SemanticVersionCreatorProperty.kt
+++ b/src/main/kotlin/creator/custom/types/SemanticVersionCreatorProperty.kt
@@ -24,6 +24,7 @@ import com.demonwav.mcdev.creator.custom.CreatorContext
import com.demonwav.mcdev.creator.custom.PropertyDerivation
import com.demonwav.mcdev.creator.custom.TemplatePropertyDescriptor
import com.demonwav.mcdev.creator.custom.TemplateValidationReporter
+import com.demonwav.mcdev.creator.custom.derivation.ExtractPaperApiVersionPropertyDerivation
import com.demonwav.mcdev.creator.custom.derivation.ExtractVersionMajorMinorPropertyDerivation
import com.demonwav.mcdev.creator.custom.derivation.PreparedDerivation
import com.demonwav.mcdev.creator.custom.derivation.SelectPropertyDerivation
@@ -64,6 +65,11 @@ open class SemanticVersionCreatorProperty(
ExtractVersionMajorMinorPropertyDerivation.create(reporter, parents, derives)
}
+ "extractPaperApiVersion" -> {
+ val parents = collectDerivationParents(reporter)
+ ExtractPaperApiVersionPropertyDerivation.create(reporter, parents, derives)
+ }
+
null -> {
SelectPropertyDerivation.create(reporter, emptyList(), derives)
}
diff --git a/src/main/kotlin/creator/custom/types/SimpleCreatorProperty.kt b/src/main/kotlin/creator/custom/types/SimpleCreatorProperty.kt
index 5f5d2a5a7..bfe405792 100644
--- a/src/main/kotlin/creator/custom/types/SimpleCreatorProperty.kt
+++ b/src/main/kotlin/creator/custom/types/SimpleCreatorProperty.kt
@@ -125,7 +125,7 @@ abstract class SimpleCreatorProperty(
isSelected: Boolean,
cellHasFocus: Boolean
): Component {
- val label = options!![value] ?: value.toString()
+ val label = options?.get(value) ?: value.toString()
return super.getListCellRendererComponent(list, label, index, isSelected, cellHasFocus)
}
}
diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml
index 62af2b7a1..376351abe 100644
--- a/src/main/resources/META-INF/plugin.xml
+++ b/src/main/resources/META-INF/plugin.xml
@@ -214,6 +214,9 @@
+
Minecraft EULA.
creator.ui.warn.no_yarn_to_mc_match=Unable to match Yarn versions to Minecraft version
creator.ui.warn.no_fabricapi_to_mc_match=Unable to match API versions to Minecraft version