kitgit

tirbofish/dropbear · commit

0f11ba0744d8561b1d70942cb973adf53c0b9082

added magna-carta plugin support for template.

Unverified

tk <4tkbytes@pm.me> · 2025-10-11 10:47

view full diff

diff --git a/.github/workflows/release-magna-carta.yaml b/.github/workflows/release-magna-carta.yaml
index 3ef132a..7f9326e 100644
--- a/.github/workflows/release-magna-carta.yaml
+++ b/.github/workflows/release-magna-carta.yaml
@@ -2,34 +2,14 @@ name: Release Magna Carta
 
 on:
   push:
-    branches:
-      - main
+    tags:
+      - 'magna-carta-v*'
 
 permissions:
   contents: write
 
 jobs:
-  check-and-release:
-    runs-on: ubuntu-latest
-    outputs:
-      should_release: ${{ steps.check-commit.outputs.should_release }}
-    steps:
-      - name: Checkout code
-        uses: actions/checkout@v4
-
-      - name: Check commit message
-        id: check-commit
-        run: |
-          COMMIT_MSG=$(git log -1 --pretty=%B)
-          if [[ "$COMMIT_MSG" == *"jarvis, create a new release for magna-carta"* ]]; then
-            echo "should_release=true" >> $GITHUB_OUTPUT
-          else
-            echo "should_release=false" >> $GITHUB_OUTPUT
-          fi
-
   build-and-release:
-    needs: check-and-release
-    if: needs.check-and-release.outputs.should_release == 'true'
     strategy:
       fail-fast: false
       matrix:
@@ -74,17 +54,9 @@ jobs:
       - name: Rename and prepare artifact
         shell: bash
         run: |
-          # Source binary path
           SRC="target/${{ matrix.target }}/release/${{ matrix.binary_name }}"
           DST="${{ matrix.artifact_name }}"
-          
-          # Copy to root for easy upload
           cp "$SRC" "./$DST"
-          
-          # Set executable permissions (Unix)
-          if [[ "${{ matrix.os }}" != "windows-latest" ]]; then
-            chmod +x "./$DST"
-          fi
 
       - name: Upload artifact
         uses: actions/upload-artifact@v4
@@ -93,8 +65,7 @@ jobs:
           path: ${{ matrix.artifact_name }}
 
   create-release:
-    needs: [check-and-release, build-and-release]
-    if: needs.check-and-release.outputs.should_release == 'true'
+    needs: build-and-release
     runs-on: ubuntu-latest
     steps:
       - name: Checkout code
@@ -105,12 +76,6 @@ jobs:
         with:
           path: artifacts
 
-      - name: Restore executable permissions (just in case)
-        run: |
-          chmod +x artifacts/magna-carta-linux-x64/magna-carta-linux-x64
-          chmod +x artifacts/magna-carta-macos-x64/magna-carta-macos-x64
-          chmod +x artifacts/magna-carta-macos-arm64/magna-carta-macos-arm64
-
       - name: Check if changelog exists
         id: changelog-check
         run: |
@@ -126,8 +91,8 @@ jobs:
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
         with:
-          tag_name: magna-carta-v${{ github.run_number }}
-          name: magna-carta release v${{ github.run_number }}
+          tag_name: ${{ github.ref_name }}  # Uses the actual tag name (e.g., magna-carta-v1.2.3)
+          name: Magna Carta Release ${{ github.ref_name }}
           body_path: ${{ steps.changelog-check.outputs.changelog_exists == 'true' && 'RELEASE_BODY.md' || '' }}
           files: |
             artifacts/magna-carta-linux-x64/magna-carta-linux-x64
diff --git a/magna-carta-plugin/build.gradle.kts b/magna-carta-plugin/build.gradle.kts
new file mode 100644
index 0000000..9f7d176
--- /dev/null
+++ b/magna-carta-plugin/build.gradle.kts
@@ -0,0 +1,28 @@
+import org.gradle.kotlin.dsl.compileOnly
+
+plugins {
+    `kotlin-dsl`
+    `maven-publish`
+}
+
+group = "com.dropbear"
+version = "1.0-SNAPSHOT"
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    implementation("de.undercouch:gradle-download-task:5.6.0")
+    compileOnly("org.jetbrains.kotlin:kotlin-gradle-plugin:${KotlinVersion.CURRENT}")
+}
+
+gradlePlugin {
+    plugins {
+        create("dropbearPlugin") {
+            id = "magna-carta"
+            implementationClass = "com.dropbear.magna_carta.MagnaCartaPlugin"
+            version = "1.0-SNAPSHOT"
+        }
+    }
+}
\ No newline at end of file
diff --git a/magna-carta-plugin/src/main/kotlin/com/dropbear/magna_carta/DownloadMagnaCartaToolTask.kt b/magna-carta-plugin/src/main/kotlin/com/dropbear/magna_carta/DownloadMagnaCartaToolTask.kt
new file mode 100644
index 0000000..4c7f2de
--- /dev/null
+++ b/magna-carta-plugin/src/main/kotlin/com/dropbear/magna_carta/DownloadMagnaCartaToolTask.kt
@@ -0,0 +1,106 @@
+package com.dropbear.magna_carta
+
+import de.undercouch.gradle.tasks.download.Download
+import org.gradle.api.GradleException
+import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.file.RegularFile
+import org.gradle.api.provider.Property
+import org.gradle.api.provider.Provider
+import org.gradle.api.tasks.*
+import org.gradle.internal.os.OperatingSystem
+import java.io.File
+import java.security.MessageDigest
+
+abstract class DownloadMagnaCartaToolTask: Download() {
+    @get:Input
+    abstract val toolVersion: Property<String>
+
+    @get:Internal
+    abstract val outputDir: DirectoryProperty
+
+    @get:OutputFile
+    val outputFile: Provider<RegularFile> = outputDir.file(getToolFileName())
+
+    @TaskAction
+    override fun download() {
+        val os = OperatingSystem.current()
+        val arch = System.getProperty("os.arch")
+
+        val (fileName, url, expectedSha256) = when {
+            os.isLinux && arch == "amd64" -> Triple(
+                "magna-carta-linux-x64",
+                "https://github.com/4tkbytes/dropbear/releases/download/${toolVersion.get()}/magna-carta-linux-x64",
+                "4cbfeca26ff0cfca5342dbd748066978041c213e6e3eb05214eec0f18a5b37b7"
+            )
+            os.isMacOsX && arch == "aarch64" -> Triple(
+                "magna-carta-macos-arm64",
+                "https://github.com/4tkbytes/dropbear/releases/download/${toolVersion.get()}/magna-carta-macos-arm64",
+                "446a89a9077fc78674342e3a16308957e9b39c347d3a313de00056630a446bb4"
+            )
+            os.isMacOsX && (arch == "x86_64" || arch == "amd64") -> Triple(
+                "magna-carta-macos-x64",
+                "https://github.com/4tkbytes/dropbear/releases/download/${toolVersion.get()}/magna-carta-macos-x64",
+                "5705f0ef43aab59e9f6b1b35fec391cceb373213f275b12d85e3614272ec0660"
+            )
+            os.isWindows && arch == "aarch64" -> Triple(
+                "magna-carta-windows-arm64.exe",
+                "https://github.com/4tkbytes/dropbear/releases/download/${toolVersion.get()}/magna-carta-windows-arm64.exe",
+                "39072fab25a69ff8c321638897b7df00d5782c6a46b578eafbaedcb9d4efc53b"
+            )
+            os.isWindows && (arch == "x86_64" || arch == "amd64") -> Triple(
+                "magna-carta-windows-x64.exe",
+                "https://github.com/4tkbytes/dropbear/releases/download/${toolVersion.get()}/magna-carta-windows-x64.exe",
+                "cce4c645da4e8a929a0f77f6685dcb104f955c19a7caba55e11d9b4c1c0ad077"
+            )
+            else -> throw GradleException("Unsupported OS/arch: $os / $arch")
+        }
+
+        val outputFile = outputDir.get().file(fileName).asFile
+        if (outputFile.exists() && verifyChecksum(outputFile, expectedSha256)) {
+            println("Using cached magna-carta tool: $outputFile")
+            return
+        }
+
+        src(url)
+        dest(outputFile)
+        overwrite(false)
+        onlyIfModified(true)
+
+        super.download()
+
+        if (!verifyChecksum(outputFile, expectedSha256)) {
+            outputFile.delete()
+            throw GradleException("Checksum verification failed for $fileName")
+        }
+
+        if (!os.isWindows) {
+            outputFile.setExecutable(true)
+        }
+    }
+
+    private fun verifyChecksum(file: File, expectedSha256: String): Boolean {
+        val digest = MessageDigest.getInstance("SHA-256")
+        file.inputStream().use { input ->
+            val buffer = ByteArray(8192)
+            var bytesRead: Int
+            while (input.read(buffer).also { bytesRead = it } > 0) {
+                digest.update(buffer, 0, bytesRead)
+            }
+        }
+        val actualSha256 = digest.digest().joinToString("") { "%02x".format(it) }
+        return actualSha256 == expectedSha256
+    }
+
+    private fun getToolFileName(): String {
+        val os = OperatingSystem.current()
+        val arch = System.getProperty("os.arch")
+        return when {
+            os.isLinux && arch == "amd64" -> "magna-carta-linux-x64"
+            os.isMacOsX && arch == "aarch64" -> "magna-carta-macos-arm64"
+            os.isMacOsX && (arch == "x86_64" || arch == "amd64") -> "magna-carta-macos-x64"
+            os.isWindows && arch == "aarch64" -> "magna-carta-windows-arm64.exe"
+            os.isWindows && (arch == "x86_64" || arch == "amd64") -> "magna-carta-windows-x64.exe"
+            else -> throw GradleException("Unsupported OS/arch: $os / $arch")
+        }
+    }
+}
\ No newline at end of file
diff --git a/magna-carta-plugin/src/main/kotlin/com/dropbear/magna_carta/GenerateMagnaCartaTask.kt b/magna-carta-plugin/src/main/kotlin/com/dropbear/magna_carta/GenerateMagnaCartaTask.kt
new file mode 100644
index 0000000..4d8b0e0
--- /dev/null
+++ b/magna-carta-plugin/src/main/kotlin/com/dropbear/magna_carta/GenerateMagnaCartaTask.kt
@@ -0,0 +1,48 @@
+package com.dropbear.magna_carta
+
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.file.RegularFileProperty
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.*
+import org.gradle.internal.os.OperatingSystem
+
+
+abstract class GenerateMagnaCartaTask : DefaultTask() {
+
+    @get:InputFile
+    @get:PathSensitive(PathSensitivity.NONE)
+    abstract val toolExecutable: RegularFileProperty
+
+    @get:Input
+    abstract val target: Property<String>
+
+    @get:InputDirectory
+    @get:PathSensitive(PathSensitivity.RELATIVE)
+    abstract val inputDir: DirectoryProperty
+
+    @get:OutputDirectory
+    abstract val outputDir: DirectoryProperty
+
+    @TaskAction
+    fun generate() {
+        val tool = toolExecutable.get().asFile
+        val input = inputDir.get().asFile
+        val output = outputDir.get().asFile
+
+        output.mkdirs()
+
+        val os = OperatingSystem.current()
+        val command = if (os.isWindows) {
+            listOf(tool.absolutePath, "--input", input.absolutePath, "--output", output.absolutePath, "--target", target.get())
+        } else {
+            listOf("bash", "-c", "${tool.absolutePath} --input ${input.absolutePath} --output ${output.absolutePath} --target ${target.get()}")
+        }
+
+        project.exec {
+            commandLine = command
+            workingDir = project.projectDir
+        }
+    }
+}
\ No newline at end of file
diff --git a/magna-carta-plugin/src/main/kotlin/com/dropbear/magna_carta/MagnaCartaPlugin.kt b/magna-carta-plugin/src/main/kotlin/com/dropbear/magna_carta/MagnaCartaPlugin.kt
new file mode 100644
index 0000000..07d254f
--- /dev/null
+++ b/magna-carta-plugin/src/main/kotlin/com/dropbear/magna_carta/MagnaCartaPlugin.kt
@@ -0,0 +1,63 @@
+package com.dropbear.magna_carta
+
+import de.undercouch.gradle.tasks.download.Download
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.file.Directory
+import org.gradle.api.tasks.Exec
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.Internal
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.kotlin.dsl.create
+import org.gradle.kotlin.dsl.get
+import org.gradle.kotlin.dsl.getByType
+import org.gradle.kotlin.dsl.register
+import org.gradle.kotlin.dsl.withType
+import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
+import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
+import java.io.File
+import java.security.MessageDigest
+
+class MagnaCartaPlugin : Plugin<Project> {
+    override fun apply(project: Project) {
+        val extension = project.extensions.create("magna-carta", MagnaCartaExtension::class)
+
+        val downloadToolTask = project.tasks.register("downloadMagnaCartaTool", DownloadMagnaCartaToolTask::class) {
+            toolVersion.set("magna-carta-v2")
+            outputDir.set(project.gradle.gradleUserHomeDir.resolve("magna-carta"))
+        }
+
+        val generateJvmTask = project.tasks.register("generateMagnaCartaJvm", GenerateMagnaCartaTask::class) {
+            dependsOn(downloadToolTask)
+            toolExecutable.set(downloadToolTask.flatMap { it.outputFile })
+            target.set("jvm")
+            inputDir.set(project.projectDir.resolve("src"))
+            outputDir.set(project.layout.buildDirectory.dir("generated/magna-carta/jvm"))
+        }
+
+        val generateNativeTask = project.tasks.register("generateMagnaCartaNative", GenerateMagnaCartaTask::class) {
+            dependsOn(downloadToolTask)
+            toolExecutable.set(downloadToolTask.flatMap { it.outputFile })
+            target.set("native")
+            inputDir.set(project.projectDir.resolve("src"))
+            outputDir.set(project.layout.buildDirectory.dir("generated/magna-carta/native"))
+        }
+
+        project.pluginManager.withPlugin("org.jetbrains.kotlin.multiplatform") {
+            val kotlin = project.extensions.getByType(KotlinMultiplatformExtension::class)
+            kotlin.sourceSets.apply {
+                val jvmMain = getByName("jvmMain")
+                jvmMain.kotlin.srcDir(generateJvmTask.map { it.outputDir })
+
+                val nativeMain = maybeCreate("nativeMain")
+                nativeMain.kotlin.srcDir(generateNativeTask.map { it.outputDir })
+
+                kotlin.targets.withType(KotlinNativeTarget::class.java) {
+                    compilations.getByName("main").defaultSourceSet.dependsOn(nativeMain)
+                }
+            }
+        }
+    }
+}
+
+abstract class MagnaCartaExtension { }
\ No newline at end of file
diff --git a/magna-carta/Cargo.toml b/magna-carta/Cargo.toml
index cce84e1..6826fc3 100644
--- a/magna-carta/Cargo.toml
+++ b/magna-carta/Cargo.toml
@@ -13,3 +13,4 @@ tree-sitter-kotlin.workspace = true
 clap = { version = "4.0", features = ["derive"] }
 serde = { version = "1.0", features = ["derive"] }
 serde_json = "1.0"
+chrono = "0.4"
\ No newline at end of file
diff --git a/magna-carta/src/generator/jvm.rs b/magna-carta/src/generator/jvm.rs
index 35f632e..a120f38 100644
--- a/magna-carta/src/generator/jvm.rs
+++ b/magna-carta/src/generator/jvm.rs
@@ -1,6 +1,8 @@
 use crate::ScriptManifest;
 use crate::generator::Generator;
 use std::fmt::Write;
+use std::collections::HashMap;
+use chrono::Utc;
 
 pub struct KotlinJVMGenerator;
 
@@ -8,11 +10,16 @@ impl Generator for KotlinJVMGenerator {
     fn generate(&self, manifest: &ScriptManifest) -> anyhow::Result<String> {
         let mut output = String::new();
 
+        let local_time = Utc::now();
+
         writeln!(
             output,
-            "// Auto-generated by dropbear-engine with the magna-carta parser"
+            "// Auto-generated by dropbear-engine with the magna-carta parser on {}",
+            local_time.format("%Y-%m-%d %H:%M:%S")
         )?;
         writeln!(output)?;
+        writeln!(output, "package com.dropbear")?;
+        writeln!(output)?;
 
         let mut imported_packages = std::collections::HashSet::new();
         for item in manifest.items() {
@@ -22,27 +29,63 @@ impl Generator for KotlinJVMGenerator {
             }
         }
 
-        for package in imported_packages.iter() {
+        for package in &imported_packages {
             writeln!(output, "import {}.*", package)?;
         }
         writeln!(output)?;
 
-        writeln!(output, "object RunnableRegistry {{")?;
-        writeln!(output, "    val SCRIPTS = listOf(")?;
-
-        for (i, item) in manifest.items().iter().enumerate() {
-            if i > 0 {
-                writeln!(output, ",")?;
+        let mut tag_map: HashMap<String, Vec<String>> = HashMap::new();
+        for item in manifest.items() {
+            let simple_name = item.simple_name();
+            if item.tags().is_empty() {
+                continue;
+            }
+            for tag in item.tags() {
+                tag_map.entry(tag.clone()).or_default().push(simple_name.to_string());
             }
-            writeln!(output, "        {}::class", item.simple_name())?;
         }
 
-        if !manifest.items().is_empty() {
-            writeln!(output)?;
+        writeln!(output, "object RunnableRegistry {{")?;
+        writeln!(output, "    private val TAG_REGISTRY = mapOf(")?;
+
+        let tag_entries: Vec<String> = tag_map
+            .iter()
+            .map(|(tag, classes)| {
+                let factories: Vec<String> = classes
+                    .iter()
+                    .map(|cls| format!("::{}", cls))
+                    .collect();
+                format!("        \"{}\" to listOf({})", tag, factories.join(", "))
+            })
+            .collect();
+
+        if !tag_entries.is_empty() {
+            writeln!(output, "{}", tag_entries.join(",\n"))?;
         }
+
         writeln!(output, "    )")?;
+        writeln!(output)?;
+
+        writeln!(
+            output,
+            "    fun getScriptFactories(tag: String): List<() -> com.dropbear.System> {{"
+        )?;
+        writeln!(output, "        return TAG_REGISTRY[tag] ?: emptyList()")?;
+        writeln!(output, "    }}")?;
+        writeln!(output)?;
+
+        writeln!(
+            output,
+            "    fun instantiateScripts(tag: String): List<com.dropbear.System> {{"
+        )?;
+        writeln!(
+            output,
+            "        return getScriptFactories(tag).map {{ it() }}"
+        )?;
+        writeln!(output, "    }}")?;
+
         writeln!(output, "}}")?;
 
         Ok(output)
     }
-}
+}
\ No newline at end of file
diff --git a/magna-carta/src/generator/mod.rs b/magna-carta/src/generator/mod.rs
index 4531335..b7ad01a 100644
--- a/magna-carta/src/generator/mod.rs
+++ b/magna-carta/src/generator/mod.rs
@@ -47,19 +47,15 @@ mod tests {
         let generator = KotlinNativeGenerator;
         let output = generator.generate(&manifest).unwrap();
 
-        // Verify imports
         assert!(output.contains("import com.game.Player"));
         assert!(output.contains("import com.game.GlobalLogger"));
 
-        // Verify tags
         assert!(output.contains("tags = listOf(\"player\", \"movement\")"));
         assert!(output.contains("tags = listOf()"));
 
-        // Verify class instantiations
         assert!(output.contains("script = Player()"));
         assert!(output.contains("script = GlobalLogger()"));
 
-        // Verify CName exports
         assert!(output.contains("@CName(\"dropbear_load\")"));
         assert!(output.contains("@CName(\"dropbear_update\")"));
         assert!(output.contains("@CName(\"dropbear_destroy\")"));
diff --git a/magna-carta/src/generator/native.rs b/magna-carta/src/generator/native.rs
index a4f2b3e..461c1b1 100644
--- a/magna-carta/src/generator/native.rs
+++ b/magna-carta/src/generator/native.rs
@@ -1,171 +1,137 @@
 use crate::ScriptManifest;
 use crate::generator::Generator;
 use std::fmt::Write;
+use std::collections::HashMap;
+use chrono::Utc;
 
 pub struct KotlinNativeGenerator;
 
 impl Generator for KotlinNativeGenerator {
     fn generate(&self, manifest: &ScriptManifest) -> anyhow::Result<String> {
         let mut output = String::new();
-
+        let local_time = Utc::now();
         writeln!(
             output,
-            "// Auto-generated by dropbear-engine with the magna-carta parser"
+            "// Auto-generated by dropbear-engine with the magna-carta parser on {}",
+            local_time.format("%Y-%m-%d %H:%M:%S")
         )?;
         writeln!(
             output,
-            "@file:OptIn(ExperimentalForeignApi::class, ExperimentalNativeApi::class)"
+            "@file:OptIn(kotlinx.cinterop.ExperimentalForeignApi::class)"
         )?;
         writeln!(output)?;
+        writeln!(output, "package com.dropbear")?;
+        writeln!(output)?;
 
         writeln!(output, "import com.dropbear.DropbearEngine")?;
         writeln!(output, "import com.dropbear.EntityId")?;
         writeln!(output, "import com.dropbear.EntityRef")?;
-        writeln!(output, "import com.dropbear.ProjectScriptingMetadata")?;
-        writeln!(output, "import com.dropbear.ScriptRegistration")?;
         writeln!(output, "import com.dropbear.ffi.NativeEngine")?;
         writeln!(output, "import kotlinx.cinterop.COpaquePointer")?;
         writeln!(output, "import kotlinx.cinterop.ExperimentalForeignApi")?;
-        writeln!(output, "import kotlin.experimental.ExperimentalNativeApi")?;
         writeln!(output, "import kotlin.native.CName")?;
         writeln!(output)?;
 
-        let mut imported_classes = std::collections::HashSet::new();
+        let mut imported_classes = Vec::new();
         for item in manifest.items() {
             if let Some(last_dot) = item.fqcn().rfind('.') {
                 let package = &item.fqcn()[..last_dot];
                 let simple_name = &item.fqcn()[last_dot + 1..];
                 writeln!(output, "import {}.{}", package, simple_name)?;
-                imported_classes.insert(simple_name.to_string());
+                imported_classes.push(simple_name.to_string());
             } else {
-                imported_classes.insert(item.simple_name().to_string());
+                imported_classes.push(item.simple_name().to_string());
             }
         }
         writeln!(output)?;
 
-        writeln!(
-            output,
-            "private val scriptInstances: List<ScriptRegistration> by lazy {{"
-        )?;
-        writeln!(output, "    Metadata().getScripts()")?;
-        writeln!(output, "}}")?;
-        writeln!(output)?;
-
-        writeln!(output, "class Metadata: ProjectScriptingMetadata {{")?;
-        writeln!(
-            output,
-            "    override fun getScripts(): List<ScriptRegistration> {{"
-        )?;
-        writeln!(output, "        return listOf(")?;
-
-        for (i, item) in manifest.items().iter().enumerate() {
-            if i > 0 {
-                writeln!(output, ",")?;
+        let mut tag_map: HashMap<String, Vec<String>> = HashMap::new();
+        for item in manifest.items() {
+            let simple_name = item.simple_name();
+            if item.tags().is_empty() {
+                continue;
+            }
+            for tag in item.tags() {
+                tag_map.entry(tag.clone()).or_default().push(simple_name.to_string());
             }
+        }
 
-            let tags_str = if item.tags().is_empty() {
-                "listOf()".to_string()
-            } else {
-                let tag_list: Vec<String> = item
-                    .tags()
-                    .iter()
-                    .map(|tag| format!("\"{}\"", tag))
-                    .collect();
-                format!("listOf({})", tag_list.join(", "))
-            };
+        writeln!(output, "private fun getScriptFactories(tag: String): List<() -> System> {{")?;
+        writeln!(output, "    return when (tag) {{")?;
 
+        for (tag, classes) in &tag_map {
+            let factories: Vec<String> = classes
+                .iter()
+                .map(|cls| format!("{{ {}() }}", cls))
+                .collect();
             writeln!(
                 output,
-                "            ScriptRegistration(\n                tags = {},\n                script = {}()\n            )",
-                tags_str,
-                item.simple_name()
+                "        \"{}\" -> listOf({})",
+                tag,
+                factories.join(", ")
             )?;
         }
 
-        if !manifest.items().is_empty() {
-            writeln!(output)?;
-        }
-        writeln!(output, "        )")?;
+        writeln!(output, "        else -> emptyList()")?;
         writeln!(output, "    }}")?;
         writeln!(output, "}}")?;
         writeln!(output)?;
 
         writeln!(
             output,
-            "fun getDropbearEngine(worldPointer: COpaquePointer?, currentEntity: Long?): DropbearEngine {{"
+            "private fun getDropbearEngine(worldPointer: COpaquePointer?, currentEntity: Long?): DropbearEngine {{"
         )?;
         writeln!(output, "    val nativeEngine = NativeEngine()")?;
         writeln!(output, "    nativeEngine.init(worldPointer)")?;
         writeln!(
             output,
-            "    val dropbearEngine = DropbearEngine(nativeEngine, if (currentEntity == null) null else EntityRef(EntityId(currentEntity)))"
-        )?;
-        writeln!(output, "    return dropbearEngine")?;
-        writeln!(output, "}}")?;
-        writeln!(output)?;
-
-        writeln!(output, "@CName(\"dropbear_load\")")?;
-        writeln!(
-            output,
-            "fun loadScriptByTag(worldPointer: COpaquePointer?, currentEntity: Long?, tag: String?) {{"
-        )?;
-        writeln!(output, "    if (tag == null) return")?;
-        writeln!(
-            output,
-            "    val scripts = scriptInstances.filter {{ it.tags.contains(tag) }}"
+            "    val entityRef = currentEntity?.let {{ EntityRef(EntityId(it)) }}"
         )?;
-        writeln!(
-            output,
-            "    val engine = getDropbearEngine(worldPointer, currentEntity)"
-        )?;
-        writeln!(output, "    for (script in scripts) {{")?;
-        writeln!(output, "        script.script.load(engine)")?;
-        writeln!(output, "    }}")?;
+        writeln!(output, "    return DropbearEngine(nativeEngine, entityRef)")?;
         writeln!(output, "}}")?;
         writeln!(output)?;
 
-        writeln!(output, "@CName(\"dropbear_update\")")?;
-        writeln!(
-            output,
-            "fun updateScriptByTag(worldPointer: COpaquePointer?, currentEntity: Long?, tag: String?, deltaTime: Double) {{"
-        )?;
-        writeln!(output, "    if (tag == null) return")?;
-        writeln!(
-            output,
-            "    val scripts = scriptInstances.filter {{ it.tags.contains(tag) }}"
-        )?;
-        writeln!(
-            output,
-            "    val engine = getDropbearEngine(worldPointer, currentEntity)"
-        )?;
-        writeln!(output, "    for (script in scripts) {{")?;
-        writeln!(
-            output,
-            "        script.script.update(engine, deltaTime.toFloat())"
-        )?;
-        writeln!(output, "    }}")?;
-        writeln!(output, "}}")?;
-        writeln!(output)?;
+        for (func_name, method_name) in [
+            ("dropbear_load", "load"),
+            ("dropbear_update", "update"),
+            ("dropbear_destroy", "destroy"),
+        ] {
+            let param_extra = if method_name == "update" {
+                ", deltaTime: Double"
+            } else {
+                ""
+            };
+            let call_extra = if method_name == "update" {
+                ", deltaTime.toFloat()"
+            } else {
+                ""
+            };
 
-        writeln!(output, "@CName(\"dropbear_destroy\")")?;
-        writeln!(
-            output,
-            "fun destroyScriptByTag(worldPointer: COpaquePointer?, currentEntity: Long?, tag: String?) {{"
-        )?;
-        writeln!(output, "    if (tag == null) return")?;
-        writeln!(
-            output,
-            "    val scripts = scriptInstances.filter {{ it.tags.contains(tag) }}"
-        )?;
-        writeln!(
-            output,
-            "    val engine = getDropbearEngine(worldPointer, currentEntity)"
-        )?;
-        writeln!(output, "    for (script in scripts) {{")?;
-        writeln!(output, "        script.script.destroy(engine)")?;
-        writeln!(output, "    }}")?;
-        writeln!(output, "}}")?;
+            writeln!(output, "@CName(\"{}\")", func_name)?;
+            writeln!(
+                output,
+                "fun {}ScriptByTag(worldPointer: COpaquePointer?, currentEntity: Long?, tag: String?{}) {{",
+                method_name,
+                param_extra
+            )?;
+            writeln!(output, "    if (tag == null) return")?;
+            writeln!(output, "    val factories = getScriptFactories(tag)")?;
+            writeln!(
+                output,
+                "    val engine = getDropbearEngine(worldPointer, currentEntity)"
+            )?;
+            writeln!(output, "    for (factory in factories) {{")?;
+            writeln!(output, "        val script = factory()")?;
+            if method_name == "update" {
+                writeln!(output, "        script.{}(engine{})", method_name, call_extra)?;
+            } else {
+                writeln!(output, "        script.{}(engine)", method_name)?;
+            }
+            writeln!(output, "    }}")?;
+            writeln!(output, "}}")?;
+            writeln!(output)?;
+        }
 
         Ok(output)
     }
-}
+}
\ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
index d12f0c0..bb7d28e 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -5,4 +5,5 @@ pluginManagement {
         mavenCentral()
         gradlePluginPortal()
     }
-}
\ No newline at end of file
+}
+include("magna-carta-plugin")
\ No newline at end of file