project re-organisation, and rename
This commit is contained in:
parent
53862ef1b5
commit
633e898f3c
52 changed files with 525 additions and 387 deletions
|
|
@ -2,6 +2,7 @@ plugins {
|
||||||
idea
|
idea
|
||||||
base
|
base
|
||||||
`project-report`
|
`project-report`
|
||||||
|
`build-dashboard`
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "dev.adamko.factoriowebmap"
|
group = "dev.adamko.factoriowebmap"
|
||||||
|
|
@ -19,6 +20,6 @@ idea {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType(HtmlDependencyReportTask::class).configureEach {
|
//tasks.withType(HtmlDependencyReportTask::class).configureEach {
|
||||||
projects = project.allprojects
|
// projects = project.allprojects
|
||||||
}
|
//}
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,6 @@ plugins {
|
||||||
group = "${rootProject.group}.${project.name}"
|
group = "${rootProject.group}.${project.name}"
|
||||||
version = rootProject.version
|
version = rootProject.version
|
||||||
|
|
||||||
val projectId by extra("fwm-${project.name}")
|
|
||||||
//val modBuildDir by extra(layout.buildDirectory.dir(projectId).get())
|
|
||||||
|
|
||||||
val licenseFile: RegularFile by extra(rootProject.layout.projectDirectory.file("LICENSE"))
|
val licenseFile: RegularFile by extra(rootProject.layout.projectDirectory.file("LICENSE"))
|
||||||
|
|
||||||
val tokens: Map<String, String> by extra(
|
val tokens: Map<String, String> by extra(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package dev.adamko.factoriowebmap.archetype
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id("dev.adamko.factoriowebmap.archetype.base")
|
||||||
|
}
|
||||||
|
|
||||||
|
val processRunnerTaskGroup: String by extra("processes")
|
||||||
|
|
||||||
|
val processRun by tasks.registering {
|
||||||
|
group = processRunnerTaskGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
val processKill by tasks.registering {
|
||||||
|
group = processRunnerTaskGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
val processRestart by tasks.registering {
|
||||||
|
group = processRunnerTaskGroup
|
||||||
|
|
||||||
|
processKill.get().state
|
||||||
|
dependsOn(processKill)
|
||||||
|
finalizedBy(processRun)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
package dev.adamko.factoriowebmap.configurations
|
||||||
|
|
||||||
|
import org.gradle.api.artifacts.Configuration
|
||||||
|
import org.gradle.api.attributes.AttributeContainer
|
||||||
|
import org.gradle.api.attributes.Bundling
|
||||||
|
import org.gradle.api.attributes.Category
|
||||||
|
import org.gradle.api.attributes.LibraryElements
|
||||||
|
import org.gradle.api.attributes.Usage
|
||||||
|
import org.gradle.api.model.ObjectFactory
|
||||||
|
import org.gradle.kotlin.dsl.named
|
||||||
|
|
||||||
|
fun Configuration.factorioModAttributes(objects: ObjectFactory) =
|
||||||
|
attributes { factorioMod(objects) }
|
||||||
|
|
||||||
|
fun AttributeContainer.factorioMod(objects: ObjectFactory) {
|
||||||
|
attribute(Usage.USAGE_ATTRIBUTE, objects.named("factorio"))
|
||||||
|
attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.LIBRARY))
|
||||||
|
attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.CLASSES_AND_RESOURCES))
|
||||||
|
attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EMBEDDED))
|
||||||
|
}
|
||||||
21
buildSrc/src/main/kotlin/dev/adamko/gradleExtensions.kt
Normal file
21
buildSrc/src/main/kotlin/dev/adamko/gradleExtensions.kt
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
package dev.adamko
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.api.Task
|
||||||
|
import org.gradle.api.specs.Spec
|
||||||
|
import org.gradle.kotlin.dsl.invoke
|
||||||
|
|
||||||
|
operator fun <T> Spec<T>.not(): Spec<T> = Spec<T> { !this(it) }
|
||||||
|
|
||||||
|
fun Project.isProcessRunning(process: String, ignoreCase: Boolean = true): Spec<Task> =
|
||||||
|
Spec<Task> {
|
||||||
|
ByteArrayOutputStream().use { outputStream ->
|
||||||
|
project.exec {
|
||||||
|
commandLine("tasklist")
|
||||||
|
standardOutput = outputStream
|
||||||
|
}
|
||||||
|
outputStream.toString()
|
||||||
|
.contains(process, ignoreCase)
|
||||||
|
}
|
||||||
|
}
|
||||||
235
modules/events-mod/build.gradle.kts
Normal file
235
modules/events-mod/build.gradle.kts
Normal file
|
|
@ -0,0 +1,235 @@
|
||||||
|
import com.github.gradle.node.npm.task.NpmTask
|
||||||
|
import dev.adamko.factoriowebmap.configurations.asConsumer
|
||||||
|
import dev.adamko.factoriowebmap.configurations.asProvider
|
||||||
|
import dev.adamko.factoriowebmap.configurations.factorioModAttributes
|
||||||
|
import dev.adamko.factoriowebmap.configurations.typescriptAttributes
|
||||||
|
import groovy.json.JsonOutput
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.JsonObject
|
||||||
|
import kotlinx.serialization.json.JsonPrimitive
|
||||||
|
import kotlinx.serialization.json.jsonObject
|
||||||
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
|
import org.apache.tools.ant.filters.ReplaceTokens
|
||||||
|
import org.gradle.kotlin.dsl.support.useToRun
|
||||||
|
import org.jetbrains.kotlin.util.parseSpaceSeparatedArgs
|
||||||
|
import org.jetbrains.kotlin.util.suffixIfNot
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
idea
|
||||||
|
id("dev.adamko.factoriowebmap.archetype.node")
|
||||||
|
}
|
||||||
|
|
||||||
|
val tokens: Map<String, String> by project.extra
|
||||||
|
val licenseFile: RegularFile by project.extra
|
||||||
|
|
||||||
|
val tsSrcDir: Directory = layout.projectDirectory.dir("src/main/typescript")
|
||||||
|
|
||||||
|
node {
|
||||||
|
nodeProjectDir.set(tsSrcDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val dataModelTs by configurations.registering {
|
||||||
|
asConsumer()
|
||||||
|
typescriptAttributes(objects)
|
||||||
|
defaultDependencies {
|
||||||
|
project.dependencies.create(projects.modules.eventsSchema)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val typescriptToLua by tasks.registering(NpmTask::class) {
|
||||||
|
description = "Convert Typescript To Lua"
|
||||||
|
group = project.name
|
||||||
|
|
||||||
|
dependsOn(tasks.npmInstall)
|
||||||
|
|
||||||
|
execOverrides { standardOutput = System.out }
|
||||||
|
|
||||||
|
npmCommand.set(listOf("run", "build"))
|
||||||
|
|
||||||
|
inputs.dir(project.node.nodeProjectDir)
|
||||||
|
.skipWhenEmpty()
|
||||||
|
.withPropertyName("sourceFiles")
|
||||||
|
.withPathSensitivity(PathSensitivity.RELATIVE)
|
||||||
|
|
||||||
|
val intermediateOutputDir = temporaryDir
|
||||||
|
args.set(parseSpaceSeparatedArgs("-- --outDir $intermediateOutputDir"))
|
||||||
|
val outputDir = layout.buildDirectory.dir("typescriptToLua")
|
||||||
|
outputs.dir(outputDir)
|
||||||
|
.withPropertyName("outputDir")
|
||||||
|
|
||||||
|
ignoreExitValue.set(false)
|
||||||
|
|
||||||
|
doFirst("clean") {
|
||||||
|
delete(intermediateOutputDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
doLast("syncTstlOutput") {
|
||||||
|
sync {
|
||||||
|
from(intermediateOutputDir)
|
||||||
|
into(outputDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
val updatePackageJsonVersion by tasks.registering {
|
||||||
|
group = project.name
|
||||||
|
description = """
|
||||||
|
Read the package.json file and set the version to be the project's version.
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
val projectVersion = "${project.version}"
|
||||||
|
inputs.properties(
|
||||||
|
"projectVersion" to projectVersion,
|
||||||
|
)
|
||||||
|
|
||||||
|
val packageJsonFile = layout.projectDirectory.file("src/main/typescript/package.json")
|
||||||
|
outputs.file(packageJsonFile)
|
||||||
|
|
||||||
|
val jsonFormatter = Json {
|
||||||
|
prettyPrint = true
|
||||||
|
prettyPrintIndent = " "
|
||||||
|
}
|
||||||
|
|
||||||
|
onlyIf {
|
||||||
|
// check to see if the version is already up-to-date
|
||||||
|
val packageJsonContent = packageJsonFile.asFile.readText()
|
||||||
|
val packageJson = jsonFormatter.parseToJsonElement(packageJsonContent).jsonObject
|
||||||
|
packageJson["version"]?.jsonPrimitive?.content != projectVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
doLast {
|
||||||
|
val packageJsonContent = packageJsonFile.asFile.readText()
|
||||||
|
val packageJson = jsonFormatter.parseToJsonElement(packageJsonContent).jsonObject
|
||||||
|
val packageJsonUpdate = JsonObject(
|
||||||
|
packageJson + ("version" to JsonPrimitive(projectVersion))
|
||||||
|
)
|
||||||
|
val packageJsonContentUpdated =
|
||||||
|
jsonFormatter
|
||||||
|
.encodeToString(packageJsonUpdate)
|
||||||
|
.suffixIfNot("\n")
|
||||||
|
packageJsonFile.asFile.writer().useToRun {
|
||||||
|
write(packageJsonContentUpdated)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val updateDataModel by tasks.registering(Sync::class) {
|
||||||
|
group = project.name
|
||||||
|
description = "Fetch the latest shared data-model"
|
||||||
|
|
||||||
|
dependsOn(dataModelTs)
|
||||||
|
|
||||||
|
from(
|
||||||
|
dataModelTs.map { c ->
|
||||||
|
c.incoming
|
||||||
|
.artifactView { lenient(true) }
|
||||||
|
.artifacts
|
||||||
|
.artifactFiles
|
||||||
|
.filter { file -> file.exists() }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
into(layout.projectDirectory.dir("src/main/typescript/model"))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val packageMod by tasks.registering(Zip::class) {
|
||||||
|
description = "Package mod files into ZIP"
|
||||||
|
group = project.name
|
||||||
|
|
||||||
|
dependsOn(typescriptToLua)
|
||||||
|
|
||||||
|
inputs.properties(tokens)
|
||||||
|
|
||||||
|
from(layout.projectDirectory.dir("src/main/resources/mod-data")) {
|
||||||
|
include("**/**")
|
||||||
|
filter<ReplaceTokens>("tokens" to tokens)
|
||||||
|
}
|
||||||
|
dependsOn(typescriptToLua)
|
||||||
|
from(licenseFile)
|
||||||
|
|
||||||
|
into(rootProject.name)
|
||||||
|
|
||||||
|
// Factorio required format is:
|
||||||
|
// - filename: `mod-name_version.zip`
|
||||||
|
// - zip contains one directory, `mod-name`
|
||||||
|
archiveFileName.set("${rootProject.name}_${project.version}.zip")
|
||||||
|
destinationDirectory.set(layout.buildDirectory.dir("dist"))
|
||||||
|
|
||||||
|
doLast {
|
||||||
|
val outDir = destinationDirectory.asFile.get().toRelativeString(layout.projectDirectory.asFile)
|
||||||
|
val outZip = "${archiveFileName.orNull}"
|
||||||
|
logger.lifecycle("Packaged mod into $outDir/$outZip")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val downloadFactorioApiDocs by tasks.registering {
|
||||||
|
group = project.name
|
||||||
|
|
||||||
|
val target = uri("https://lua-api.factorio.com/latest/runtime-api.json")
|
||||||
|
val apiFilename = File(target.path).name
|
||||||
|
val downloadedFile = file("$temporaryDir/$apiFilename")
|
||||||
|
|
||||||
|
val apiFile = layout.buildDirectory.file(apiFilename)
|
||||||
|
outputs.file(apiFile)
|
||||||
|
|
||||||
|
doLast {
|
||||||
|
|
||||||
|
ant.invokeMethod(
|
||||||
|
"get", mapOf(
|
||||||
|
"src" to target,
|
||||||
|
"dest" to downloadedFile,
|
||||||
|
"verbose" to true,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val json = downloadedFile.readText()
|
||||||
|
val prettyJson = JsonOutput.prettyPrint(json)
|
||||||
|
|
||||||
|
apiFile.get().asFile.writeText(prettyJson)
|
||||||
|
|
||||||
|
logger.lifecycle("Downloaded Factorio API json: $apiFile")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val factorioModProvider by configurations.registering {
|
||||||
|
asProvider()
|
||||||
|
factorioModAttributes(objects)
|
||||||
|
outgoing.artifact(packageMod)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
tasks.assemble { dependsOn(updateDataModel, updatePackageJsonVersion) }
|
||||||
|
tasks.build { dependsOn(packageMod) }
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
//idea {
|
||||||
|
// // this doesn't work - no source sets?
|
||||||
|
// module {
|
||||||
|
// sourceDirs.add(mkdir(layout.projectDirectory.dir("src/main/typescript").asFile))
|
||||||
|
// excludeDirs.add(mkdir(layout.projectDirectory.dir("src/main/typescript/node_modules").asFile))
|
||||||
|
// resourceDirs.add(mkdir(layout.projectDirectory.dir("src/main/resources").asFile))
|
||||||
|
// resourceDirs.add(mkdir(layout.projectDirectory.dir("infra").asFile))
|
||||||
|
// excludeDirs.add(mkdir(layout.projectDirectory.dir("infra/factorio-server").asFile))
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//val typescriptSrcSet =
|
||||||
|
// project.objects.sourceDirectorySet("typescript", "typescript").apply {
|
||||||
|
// srcDir(layout.projectDirectory.dir("src/main/typescript"))
|
||||||
|
//
|
||||||
|
// destinationDirectory.set(modBuildDir.dir("typescriptToLua").asFile)
|
||||||
|
// compiledBy(tstlTask) {
|
||||||
|
//
|
||||||
|
// project.objects.directoryProperty().apply {
|
||||||
|
// set(modBuildDir.dir("typescriptToLua"))
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
Before Width: | Height: | Size: 702 B After Width: | Height: | Size: 702 B |
|
|
@ -43,4 +43,4 @@ interface SurfaceData extends FactorioObjectData {
|
||||||
index: uint;
|
index: uint;
|
||||||
name: string;
|
name: string;
|
||||||
object_name: string;
|
object_name: string;
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "factorio-web-map-mod",
|
"name": "factorio-web-map-mod",
|
||||||
"version": "0.0.6",
|
"version": "0.0.7",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -9,8 +9,7 @@
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.13.0"
|
"node": ">=10.13.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {},
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lua-types": "^2.11.0",
|
"lua-types": "^2.11.0",
|
||||||
"@types/node": "^12.0.8",
|
"@types/node": "^12.0.8",
|
||||||
|
|
@ -21,10 +20,7 @@
|
||||||
"//": {
|
"//": {
|
||||||
"typed-factorio": "0.10.0"
|
"typed-factorio": "0.10.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {},
|
||||||
},
|
"optionalDependencies": {},
|
||||||
"optionalDependencies": {
|
"bundledDependencies": []
|
||||||
},
|
|
||||||
"bundledDependencies": [
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
@ -11,14 +11,13 @@ description = """
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
|
||||||
val projectId: String by project.extra
|
val projectId: String by project.extra
|
||||||
val buildDir: Directory = layout.buildDirectory.dir(projectId).get()
|
|
||||||
|
|
||||||
@Suppress("UnstableApiUsage")
|
@Suppress("UnstableApiUsage")
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
implementation("org.apache.kafka:kafka-streams:3.0.0")
|
implementation("org.apache.kafka:kafka-streams:3.0.0")
|
||||||
|
|
||||||
implementation(projects.modules.factorioEventsDataModel)
|
implementation(projects.modules.eventsSchema)
|
||||||
|
|
||||||
// implementation("com.ionspin.kotlin:bignum:0.3.3")
|
// implementation("com.ionspin.kotlin:bignum:0.3.3")
|
||||||
|
|
||||||
|
|
@ -6,9 +6,6 @@ plugins {
|
||||||
kotlin("plugin.serialization")
|
kotlin("plugin.serialization")
|
||||||
}
|
}
|
||||||
|
|
||||||
val projectId: String by project.extra
|
|
||||||
val buildDir: Directory = layout.buildDirectory.dir(projectId).get()
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(platform(libs.kotlinx.serialization.bom))
|
implementation(platform(libs.kotlinx.serialization.bom))
|
||||||
implementation(libs.kotlinx.serialization.core)
|
implementation(libs.kotlinx.serialization.core)
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
import org.jetbrains.kotlin.util.parseSpaceSeparatedArgs
|
|
||||||
|
|
||||||
plugins {
|
|
||||||
id("dev.adamko.factoriowebmap.archetype.base")
|
|
||||||
}
|
|
||||||
|
|
||||||
description =
|
|
||||||
"""
|
|
||||||
Send events from a Factorio server to a Kafka topic
|
|
||||||
""".trimIndent()
|
|
||||||
|
|
||||||
val projectId: String by project.extra
|
|
||||||
81
modules/infra-factorio-client/build.gradle.kts
Normal file
81
modules/infra-factorio-client/build.gradle.kts
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
import dev.adamko.factoriowebmap.configurations.asConsumer
|
||||||
|
import dev.adamko.factoriowebmap.configurations.factorioModAttributes
|
||||||
|
import dev.adamko.isProcessRunning
|
||||||
|
import dev.adamko.not
|
||||||
|
import org.jetbrains.kotlin.util.parseSpaceSeparatedArgs
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id("dev.adamko.factoriowebmap.archetype.process-runner")
|
||||||
|
}
|
||||||
|
|
||||||
|
val tokens: Map<String, String> by project.extra
|
||||||
|
|
||||||
|
val clientModsDirectory = file("""D:\Users\Adam\AppData\Roaming\Factorio\mods""")
|
||||||
|
val factorioGameId = "427520"
|
||||||
|
val steamExe = """C:\Program Files (x86)\Steam\steam.exe"""
|
||||||
|
|
||||||
|
val factorioMod: Configuration by configurations.creating {
|
||||||
|
asConsumer()
|
||||||
|
factorioModAttributes(objects)
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
factorioMod(projects.modules.eventsMod)
|
||||||
|
}
|
||||||
|
|
||||||
|
//<editor-fold desc="Mod deployment tasks">
|
||||||
|
val deployModToClient by tasks.registering(Copy::class) {
|
||||||
|
description = "Copy the mod to the Factorio client"
|
||||||
|
group = project.name
|
||||||
|
|
||||||
|
from(factorioMod)
|
||||||
|
into(clientModsDirectory)
|
||||||
|
|
||||||
|
doLast {
|
||||||
|
logger.lifecycle("Copying mod from ${source.files} to $destinationDir")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//</editor-fold>
|
||||||
|
|
||||||
|
//<editor-fold desc="Factorio client lifecycle tasks">
|
||||||
|
fun isFactorioRunning(): Spec<Task> = isProcessRunning("factorio.exe")
|
||||||
|
|
||||||
|
val clientLaunch by tasks.registering(Exec::class) {
|
||||||
|
description = "Run Factorio Steam game client"
|
||||||
|
group = project.name
|
||||||
|
|
||||||
|
onlyIf(!isFactorioRunning())
|
||||||
|
|
||||||
|
dependsOn(deployModToClient)
|
||||||
|
mustRunAfter(clientKill)
|
||||||
|
|
||||||
|
commandLine = parseSpaceSeparatedArgs(
|
||||||
|
// """explorer "steam://rungameid/$factorioGameId// --mp-connect localhost/" """ // not working
|
||||||
|
// """explorer "steam://run/$factorioGameId//--mp-connect localhost/" """ // works! But has annoying pop-up
|
||||||
|
""" $steamExe -applaunch $factorioGameId --mp-connect localhost """
|
||||||
|
)
|
||||||
|
doFirst { logger.lifecycle("Launching factorio.exe") }
|
||||||
|
}
|
||||||
|
|
||||||
|
val clientKill by tasks.registering(Exec::class) {
|
||||||
|
description = "Run Factorio Steam game client"
|
||||||
|
group = project.name
|
||||||
|
onlyIf(isFactorioRunning())
|
||||||
|
commandLine = parseSpaceSeparatedArgs(""" taskkill /im factorio.exe """)
|
||||||
|
doFirst { logger.lifecycle("Killing factorio.exe") }
|
||||||
|
}
|
||||||
|
//</editor-fold>
|
||||||
|
|
||||||
|
tasks.build { dependsOn(deployModToClient) }
|
||||||
|
|
||||||
|
tasks.processRun {
|
||||||
|
dependsOn(
|
||||||
|
deployModToClient,
|
||||||
|
clientLaunch,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.processKill {
|
||||||
|
dependsOn(clientKill)
|
||||||
|
}
|
||||||
|
|
||||||
77
modules/infra-factorio-server/build.gradle.kts
Normal file
77
modules/infra-factorio-server/build.gradle.kts
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
import dev.adamko.factoriowebmap.configurations.asConsumer
|
||||||
|
import dev.adamko.factoriowebmap.configurations.factorioModAttributes
|
||||||
|
import org.jetbrains.kotlin.util.parseSpaceSeparatedArgs
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id("dev.adamko.factoriowebmap.archetype.process-runner")
|
||||||
|
}
|
||||||
|
|
||||||
|
val tokens: Map<String, String> by project.extra
|
||||||
|
|
||||||
|
val modInfraDir: Directory = layout.projectDirectory.dir("infra")
|
||||||
|
val factorioServerDataDir: Directory = modInfraDir.dir("factorio-server")
|
||||||
|
|
||||||
|
val factorioMod: Configuration by configurations.creating {
|
||||||
|
asConsumer()
|
||||||
|
factorioModAttributes(objects)
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
factorioMod(projects.modules.eventsMod)
|
||||||
|
}
|
||||||
|
|
||||||
|
val deployModToServer by tasks.registering(Copy::class) {
|
||||||
|
description = "Copy the mod to the Factorio Docker server"
|
||||||
|
group = project.name
|
||||||
|
|
||||||
|
from(factorioMod)
|
||||||
|
into(factorioServerDataDir.dir("mods"))
|
||||||
|
|
||||||
|
doLast {
|
||||||
|
logger.lifecycle("Copying mods ${source.files} to $destinationDir")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//<editor-fold desc="Factorio server lifecycle tasks">
|
||||||
|
val factorioServerStop = tasks.register<Exec>("factorioServerStop") {
|
||||||
|
group = project.name
|
||||||
|
|
||||||
|
mustRunAfter(deployModToServer)
|
||||||
|
|
||||||
|
workingDir(modInfraDir)
|
||||||
|
commandLine = parseSpaceSeparatedArgs("docker-compose stop factorio-server")
|
||||||
|
}
|
||||||
|
|
||||||
|
val factorioServerUp = tasks.register<Exec>("factorioServerUp") {
|
||||||
|
group = project.name
|
||||||
|
|
||||||
|
mustRunAfter(deployModToServer)
|
||||||
|
dependsOn(factorioServerStop)
|
||||||
|
|
||||||
|
workingDir(modInfraDir)
|
||||||
|
commandLine = parseSpaceSeparatedArgs("docker-compose up -d factorio-server")
|
||||||
|
}
|
||||||
|
|
||||||
|
val factorioServerRestart: Task by tasks.creating {
|
||||||
|
group = project.name
|
||||||
|
|
||||||
|
dependsOn(factorioServerStop, factorioServerUp)
|
||||||
|
}
|
||||||
|
//</editor-fold>
|
||||||
|
|
||||||
|
tasks.build { dependsOn(deployModToServer) }
|
||||||
|
|
||||||
|
tasks.processRun {
|
||||||
|
description = "Build the mod, upload to Server and Client, and start both"
|
||||||
|
dependsOn(
|
||||||
|
deployModToServer,
|
||||||
|
factorioServerRestart,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.processKill {
|
||||||
|
dependsOn(
|
||||||
|
factorioServerStop,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
39
modules/infra-factorio-server/infra/docker-compose.yml
Normal file
39
modules/infra-factorio-server/infra/docker-compose.yml
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
version: "3.9"
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
factorio-server:
|
||||||
|
image: factoriotools/factorio:1.1.48
|
||||||
|
# logging:
|
||||||
|
# driver: "json-file"
|
||||||
|
container_name: "factorio-server"
|
||||||
|
restart: "unless-stopped"
|
||||||
|
networks:
|
||||||
|
- "factorio-events-nw"
|
||||||
|
ports:
|
||||||
|
- "34197:34197/udp" # factorio
|
||||||
|
- "27015:27015/tcp" # rcon
|
||||||
|
volumes:
|
||||||
|
- "./factorio-server:/factorio"
|
||||||
|
# logging:
|
||||||
|
# driver: "mickyg/kafka-logdriver:latest"
|
||||||
|
# options:
|
||||||
|
# KAFKA_BROKER_ADDR: "kafka-kraft"
|
||||||
|
# LOG_TOPIC: "dockerlogs-factorio-server"
|
||||||
|
# LOG_TAG: "factorio-server"
|
||||||
|
|
||||||
|
# environment:
|
||||||
|
# RCON_PORT: 27715
|
||||||
|
# environment:
|
||||||
|
# - PUID=1000
|
||||||
|
# - PGID=1000
|
||||||
|
# - UPDATE_MODS_ON_START=true
|
||||||
|
# - USERNAME=FactorioUsername
|
||||||
|
# - TOKEN=FactorioToken
|
||||||
|
|
||||||
|
networks:
|
||||||
|
kafka-nw:
|
||||||
|
name: "kafka-nw"
|
||||||
|
external: true
|
||||||
|
factorio-events-nw:
|
||||||
|
name: "factorio-events-nw"
|
||||||
|
|
@ -2,35 +2,6 @@ version: "3.9"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
|
||||||
factorio-server:
|
|
||||||
image: factoriotools/factorio:1.1.48
|
|
||||||
# logging:
|
|
||||||
# driver: "json-file"
|
|
||||||
container_name: "factorio-server"
|
|
||||||
restart: "unless-stopped"
|
|
||||||
networks:
|
|
||||||
- "factorio-events-nw"
|
|
||||||
ports:
|
|
||||||
- "34197:34197/udp" # factorio
|
|
||||||
- "27015:27015/tcp" # rcon
|
|
||||||
volumes:
|
|
||||||
- "./factorio-server:/factorio"
|
|
||||||
# logging:
|
|
||||||
# driver: "mickyg/kafka-logdriver:latest"
|
|
||||||
# options:
|
|
||||||
# KAFKA_BROKER_ADDR: "kafka-kraft"
|
|
||||||
# LOG_TOPIC: "dockerlogs-factorio-server"
|
|
||||||
# LOG_TAG: "factorio-server"
|
|
||||||
|
|
||||||
# environment:
|
|
||||||
# RCON_PORT: 27715
|
|
||||||
# environment:
|
|
||||||
# - PUID=1000
|
|
||||||
# - PGID=1000
|
|
||||||
# - UPDATE_MODS_ON_START=true
|
|
||||||
# - USERNAME=FactorioUsername
|
|
||||||
# - TOKEN=FactorioToken
|
|
||||||
|
|
||||||
kafka:
|
kafka:
|
||||||
image: dev-adamko/kafka-kraft
|
image: dev-adamko/kafka-kraft
|
||||||
container_name: "kafka"
|
container_name: "kafka"
|
||||||
|
|
@ -74,10 +45,9 @@ services:
|
||||||
# - "8083:8083"
|
# - "8083:8083"
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
kafka-nw:
|
kafka-nw:
|
||||||
name: "kafka-nw"
|
name: "kafka-nw"
|
||||||
factorio-events-nw:
|
factorio-events-nw:
|
||||||
name: "factorio-events-nw"
|
name: "factorio-events-nw"
|
||||||
|
external: true
|
||||||
6
modules/infra-kafka-pipe/build.gradle.kts
Normal file
6
modules/infra-kafka-pipe/build.gradle.kts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
plugins {
|
||||||
|
id("dev.adamko.factoriowebmap.archetype.process-runner")
|
||||||
|
}
|
||||||
|
|
||||||
|
description = "Send events from a Factorio server to a Kafka topic "
|
||||||
|
|
||||||
|
|
@ -1,318 +0,0 @@
|
||||||
import com.github.gradle.node.npm.task.NpmTask
|
|
||||||
import dev.adamko.factoriowebmap.configurations.asConsumer
|
|
||||||
import dev.adamko.factoriowebmap.configurations.typescriptAttributes
|
|
||||||
import groovy.json.JsonOutput
|
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import kotlinx.serialization.json.JsonObject
|
|
||||||
import kotlinx.serialization.json.JsonPrimitive
|
|
||||||
import kotlinx.serialization.json.jsonObject
|
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
|
||||||
import org.apache.tools.ant.filters.ReplaceTokens
|
|
||||||
import org.gradle.kotlin.dsl.support.useToRun
|
|
||||||
import org.jetbrains.kotlin.util.parseSpaceSeparatedArgs
|
|
||||||
import org.jetbrains.kotlin.util.suffixIfNot
|
|
||||||
|
|
||||||
plugins {
|
|
||||||
idea
|
|
||||||
id("dev.adamko.factoriowebmap.archetype.node")
|
|
||||||
}
|
|
||||||
|
|
||||||
val projectId: String by project.extra
|
|
||||||
val tokens: Map<String, String> by project.extra
|
|
||||||
|
|
||||||
val tsSrcDir: Directory = layout.projectDirectory.dir("src/main/typescript")
|
|
||||||
val modBuildDir: Directory = layout.buildDirectory.dir(projectId).get()
|
|
||||||
|
|
||||||
node {
|
|
||||||
nodeProjectDir.set(tsSrcDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
val tstlTask = tasks.register<NpmTask>("typescriptToLua") {
|
|
||||||
description = "Convert Typescript To Lua"
|
|
||||||
group = projectId
|
|
||||||
|
|
||||||
dependsOn(tasks.npmInstall)
|
|
||||||
|
|
||||||
execOverrides { standardOutput = System.out }
|
|
||||||
|
|
||||||
npmCommand.set(listOf("run", "build"))
|
|
||||||
|
|
||||||
inputs.dir(project.node.nodeProjectDir)
|
|
||||||
.skipWhenEmpty()
|
|
||||||
.withPropertyName("sourceFiles")
|
|
||||||
.withPathSensitivity(PathSensitivity.RELATIVE)
|
|
||||||
|
|
||||||
val intermediateOutputDir = temporaryDir
|
|
||||||
args.set(parseSpaceSeparatedArgs("-- --outDir $intermediateOutputDir"))
|
|
||||||
val outputDir: Directory = modBuildDir.dir("typescriptToLua")
|
|
||||||
outputs.dir(outputDir)
|
|
||||||
.withPropertyName("outputDir")
|
|
||||||
|
|
||||||
ignoreExitValue.set(false)
|
|
||||||
|
|
||||||
doFirst("clean") {
|
|
||||||
delete(intermediateOutputDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
doLast("syncTstlOutput") {
|
|
||||||
sync {
|
|
||||||
from(intermediateOutputDir)
|
|
||||||
into(outputDir)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
val updatePackageJsonVersion by tasks.registering {
|
|
||||||
group = project.name
|
|
||||||
description = """
|
|
||||||
Read the package.json file and set the version to be the project's version.
|
|
||||||
""".trimIndent()
|
|
||||||
|
|
||||||
val projectVersion = "${project.version}"
|
|
||||||
inputs.properties(
|
|
||||||
"projectVersion" to projectVersion,
|
|
||||||
)
|
|
||||||
|
|
||||||
val packageJsonFile = layout.projectDirectory.file("src/main/typescript/package.json")
|
|
||||||
outputs.file(packageJsonFile)
|
|
||||||
|
|
||||||
val jsonFormatter = Json {
|
|
||||||
prettyPrint = true
|
|
||||||
prettyPrintIndent = " "
|
|
||||||
}
|
|
||||||
|
|
||||||
onlyIf {
|
|
||||||
val packageJsonContent = packageJsonFile.asFile.readText()
|
|
||||||
val packageJson = jsonFormatter.parseToJsonElement(packageJsonContent).jsonObject
|
|
||||||
packageJson["version"]?.jsonPrimitive?.content != projectVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
doFirst {
|
|
||||||
delete(temporaryDir)
|
|
||||||
mkdir(temporaryDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
doLast {
|
|
||||||
val packageJsonContent = packageJsonFile.asFile.readText()
|
|
||||||
val packageJson = jsonFormatter.parseToJsonElement(packageJsonContent).jsonObject
|
|
||||||
val packageJsonUpdate = JsonObject(
|
|
||||||
packageJson + ("version" to JsonPrimitive(projectVersion))
|
|
||||||
)
|
|
||||||
val packageJsonContentUpdated =
|
|
||||||
jsonFormatter
|
|
||||||
.encodeToString(packageJsonUpdate)
|
|
||||||
.suffixIfNot("\n")
|
|
||||||
packageJsonFile.asFile.writer().useToRun {
|
|
||||||
write(packageJsonContentUpdated)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.assemble { dependsOn(updatePackageJsonVersion) }
|
|
||||||
|
|
||||||
|
|
||||||
val dataModelTs: Configuration by configurations.creating {
|
|
||||||
asConsumer()
|
|
||||||
typescriptAttributes(objects)
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
dataModelTs(projects.modules.factorioEventsDataModel)
|
|
||||||
}
|
|
||||||
|
|
||||||
val updateDataModel by tasks.registering(Sync::class) {
|
|
||||||
group = project.name
|
|
||||||
|
|
||||||
dependsOn(dataModelTs)
|
|
||||||
|
|
||||||
from(
|
|
||||||
dataModelTs.incoming
|
|
||||||
.artifactView { lenient(true) }
|
|
||||||
.artifacts
|
|
||||||
.artifactFiles
|
|
||||||
.filter { it.exists() }
|
|
||||||
)
|
|
||||||
|
|
||||||
into(layout.projectDirectory.dir("src/main/typescript/generated"))
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.assemble { dependsOn(updateDataModel) }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
idea {
|
|
||||||
module {
|
|
||||||
sourceDirs.add(mkdir(layout.projectDirectory.dir("src/main/typescript").asFile))
|
|
||||||
excludeDirs.add(mkdir(layout.projectDirectory.dir("src/main/typescript/node_modules").asFile))
|
|
||||||
resourceDirs.add(mkdir(layout.projectDirectory.dir("src/main/resources").asFile))
|
|
||||||
resourceDirs.add(mkdir(layout.projectDirectory.dir("infra").asFile))
|
|
||||||
excludeDirs.add(mkdir(layout.projectDirectory.dir("infra/factorio-server").asFile))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val typescriptSrcSet =
|
|
||||||
project.objects.sourceDirectorySet("typescript", "typescript").apply {
|
|
||||||
srcDir(layout.projectDirectory.dir("src/main/typescript"))
|
|
||||||
|
|
||||||
destinationDirectory.set(modBuildDir.dir("typescriptToLua").asFile)
|
|
||||||
compiledBy(tstlTask) {
|
|
||||||
|
|
||||||
project.objects.directoryProperty().apply {
|
|
||||||
set(modBuildDir.dir("typescriptToLua"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val modPackageTask = tasks.register<Zip>("packageMod") {
|
|
||||||
description = "Package mod files into ZIP"
|
|
||||||
group = projectId
|
|
||||||
|
|
||||||
dependsOn(tstlTask)
|
|
||||||
|
|
||||||
inputs.properties(tokens)
|
|
||||||
|
|
||||||
from(layout.projectDirectory.dir("src/main/resources/mod-data")) {
|
|
||||||
include("**/**")
|
|
||||||
filter<ReplaceTokens>("tokens" to tokens)
|
|
||||||
}
|
|
||||||
from(
|
|
||||||
rootProject.layout.projectDirectory.file("LICENSE"),
|
|
||||||
tstlTask,
|
|
||||||
)
|
|
||||||
|
|
||||||
into(rootProject.name)
|
|
||||||
|
|
||||||
// Factorio required format is:
|
|
||||||
// - filename: `mod-name_version.zip`
|
|
||||||
// - zip contains one directory, `mod-name`
|
|
||||||
archiveFileName.set("${rootProject.name}_${project.version}.zip")
|
|
||||||
destinationDirectory.set(modBuildDir.dir("dist"))
|
|
||||||
|
|
||||||
doLast {
|
|
||||||
val outDir = destinationDirectory.asFile.get().toRelativeString(layout.projectDirectory.asFile)
|
|
||||||
val outZip = "${archiveFileName.orNull}"
|
|
||||||
logger.lifecycle("Creating mod zip $outDir/$outZip")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//tasks.build { dependsOn(tstlTask, modPackageTask) }
|
|
||||||
|
|
||||||
val modInfraDir: Directory = layout.projectDirectory.dir("infra")
|
|
||||||
val factorioServerDataDir: Directory = modInfraDir.dir("factorio-server")
|
|
||||||
|
|
||||||
val copyModToServerTask = tasks.register<Copy>("copyModToServer") {
|
|
||||||
description = "Copy the mod to the Factorio Docker server"
|
|
||||||
group = "$projectId.factorioServer"
|
|
||||||
|
|
||||||
dependsOn(modPackageTask)
|
|
||||||
|
|
||||||
from(modPackageTask)
|
|
||||||
into(factorioServerDataDir.dir("mods"))
|
|
||||||
|
|
||||||
doLast {
|
|
||||||
logger.lifecycle("Copying mod from ${source.files} to $destinationDir")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val factorioServerStop = tasks.register<Exec>("factorioServerStop") {
|
|
||||||
group = "$projectId.factorioServer"
|
|
||||||
|
|
||||||
mustRunAfter(copyModToServerTask)
|
|
||||||
|
|
||||||
workingDir(modInfraDir)
|
|
||||||
commandLine = parseSpaceSeparatedArgs("docker-compose stop factorio-server")
|
|
||||||
}
|
|
||||||
|
|
||||||
val factorioServerUp = tasks.register<Exec>("factorioServerUp") {
|
|
||||||
group = "$projectId.factorioServer"
|
|
||||||
|
|
||||||
mustRunAfter(copyModToServerTask)
|
|
||||||
dependsOn(factorioServerStop)
|
|
||||||
|
|
||||||
workingDir(modInfraDir)
|
|
||||||
commandLine = parseSpaceSeparatedArgs("docker-compose up -d factorio-server")
|
|
||||||
}
|
|
||||||
|
|
||||||
val factorioServerRestart: Task by tasks.creating {
|
|
||||||
group = "$projectId.factorioServer"
|
|
||||||
|
|
||||||
dependsOn(factorioServerStop, factorioServerUp)
|
|
||||||
}
|
|
||||||
|
|
||||||
//val serverRestartTask = tasks.register<Exec>("dockerRestart") {
|
|
||||||
// group = "$projectId.factorioServer"
|
|
||||||
//
|
|
||||||
// workingDir = layout.projectDirectory.dir("src/test/resources/server/").asFile
|
|
||||||
// commandLine = parseSpaceSeparatedArgs("docker-compose restart")
|
|
||||||
//}
|
|
||||||
|
|
||||||
val copyModToClientTask = tasks.register<Copy>("copyModToClient") {
|
|
||||||
description = "Copy the mod to the Factorio client"
|
|
||||||
group = "$projectId.factorioClient"
|
|
||||||
|
|
||||||
dependsOn(modPackageTask)
|
|
||||||
|
|
||||||
from(modPackageTask)
|
|
||||||
into("""D:\Users\Adam\AppData\Roaming\Factorio\mods""")
|
|
||||||
|
|
||||||
doLast {
|
|
||||||
logger.lifecycle("Copying mod from ${source.files} to $destinationDir")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//val factorioClientLaunch = tasks.register<Exec>("launch") {
|
|
||||||
// description = "Run Factorio client"
|
|
||||||
// group = "$projectId.factorioClient"
|
|
||||||
//
|
|
||||||
// dependsOn(
|
|
||||||
// serverRestartTask,
|
|
||||||
// copyModToClientTask
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
//// workingDir = layout.projectDirectory.dir("src/test/resources/server/").asFile
|
|
||||||
// commandLine = parseSpaceSeparatedArgs(
|
|
||||||
// """explorer "steam://rungameid/427520 --mp-connect localhost" """
|
|
||||||
// )
|
|
||||||
//}
|
|
||||||
|
|
||||||
val launch = tasks.register("modLauncher") {
|
|
||||||
description = "Build the mod, upload to Server and Client, and start both"
|
|
||||||
group = projectId
|
|
||||||
dependsOn(
|
|
||||||
copyModToServerTask,
|
|
||||||
copyModToClientTask,
|
|
||||||
factorioServerRestart,
|
|
||||||
// factorioClientLaunch,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.register("downloadFactorioApiDocs") {
|
|
||||||
group = projectId
|
|
||||||
|
|
||||||
val target = uri("https://lua-api.factorio.com/latest/runtime-api.json")
|
|
||||||
val apiFilename = File(target.path).name
|
|
||||||
val downloadedFile = file("$temporaryDir/$apiFilename")
|
|
||||||
|
|
||||||
val apiFile = modBuildDir.file(apiFilename)
|
|
||||||
outputs.file(apiFile)
|
|
||||||
|
|
||||||
doLast {
|
|
||||||
|
|
||||||
ant.invokeMethod(
|
|
||||||
"get", mapOf(
|
|
||||||
"src" to target,
|
|
||||||
"dest" to downloadedFile,
|
|
||||||
"verbose" to true,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
val json = downloadedFile.readText()
|
|
||||||
val prettyJson = JsonOutput.prettyPrint(json)
|
|
||||||
|
|
||||||
apiFile.asFile.writeText(prettyJson)
|
|
||||||
|
|
||||||
logger.lifecycle("Downloaded Factorio API json: $apiFile")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +1,17 @@
|
||||||
rootProject.name = "factorio-web-map"
|
rootProject.name = "kafkatorio"
|
||||||
|
|
||||||
include(
|
include(
|
||||||
":modules:mod",
|
|
||||||
|
":modules:events-mod",
|
||||||
|
":modules:events-processor",
|
||||||
|
":modules:events-schema",
|
||||||
|
|
||||||
|
":modules:infra-factorio-client",
|
||||||
|
":modules:infra-factorio-server",
|
||||||
|
":modules:infra-kafka-cluster",
|
||||||
|
":modules:infra-kafka-pipe",
|
||||||
|
|
||||||
":modules:kt-rcon",
|
":modules:kt-rcon",
|
||||||
":modules:factorio-events-data-model",
|
|
||||||
":modules:factorio-events-processor",
|
|
||||||
":modules:factorio-events-kafka-pipe",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
enableFeaturePreview("VERSION_CATALOGS")
|
enableFeaturePreview("VERSION_CATALOGS")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue