From 6178a82d24fb9b2140678838529a709ef45b5a4d Mon Sep 17 00:00:00 2001 From: SikongJueluo Date: Wed, 21 Jan 2026 23:53:41 +0800 Subject: [PATCH] feat(types): add DataBus system with auto-generated type declarations --- bun.lock | 40 +++ jsconfig.json | 1 + package.json | 10 +- scripts/AutoExport.js | 453 +++++++++++++++++++++++++++++++++ src/server_scripts/C4.js | 8 +- src/startup_scripts/C4.js | 66 ++++- src/startup_scripts/DataBus.js | 62 +++++ types/DataBus.d.ts | 93 +++++++ 8 files changed, 718 insertions(+), 15 deletions(-) create mode 100644 scripts/AutoExport.js create mode 100644 types/DataBus.d.ts diff --git a/bun.lock b/bun.lock index b5b1f0e..7817bd3 100644 --- a/bun.lock +++ b/bun.lock @@ -4,11 +4,41 @@ "workspaces": { "": { "devDependencies": { + "@babel/generator": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6", "oxlint": "^1.39.0", }, }, }, "packages": { + "@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], + + "@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], + + "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + + "@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], + + "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + + "@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="], + + "@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + "@oxlint/darwin-arm64": ["@oxlint/darwin-arm64@1.39.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-lT3hNhIa02xCujI6YGgjmYGg3Ht/X9ag5ipUVETaMpx5Rd4BbTNWUPif1WN1YZHxt3KLCIqaAe7zVhatv83HOQ=="], "@oxlint/darwin-x64": ["@oxlint/darwin-x64@1.39.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-UT+rfTWd+Yr7iJeSLd/7nF8X4gTYssKh+n77hxl6Oilp3NnG1CKRHxZDy3o3lIBnwgzJkdyUAiYWO1bTMXQ1lA=="], @@ -25,6 +55,16 @@ "@oxlint/win32-x64": ["@oxlint/win32-x64@1.39.0", "", { "os": "win32", "cpu": "x64" }, "sha512-sbi25lfj74hH+6qQtb7s1wEvd1j8OQbTaH8v3xTcDjrwm579Cyh0HBv1YSZ2+gsnVwfVDiCTL1D0JsNqYXszVA=="], + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "oxlint": ["oxlint@1.39.0", "", { "optionalDependencies": { "@oxlint/darwin-arm64": "1.39.0", "@oxlint/darwin-x64": "1.39.0", "@oxlint/linux-arm64-gnu": "1.39.0", "@oxlint/linux-arm64-musl": "1.39.0", "@oxlint/linux-x64-gnu": "1.39.0", "@oxlint/linux-x64-musl": "1.39.0", "@oxlint/win32-arm64": "1.39.0", "@oxlint/win32-x64": "1.39.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.10.0" }, "optionalPeers": ["oxlint-tsgolint"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-wSiLr0wjG+KTU6c1LpVoQk7JZ7l8HCKlAkVDVTJKWmCGazsNxexxnOXl7dsar92mQcRnzko5g077ggP3RINSjA=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], } } diff --git a/jsconfig.json b/jsconfig.json index 845c2ce..9b58c46 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -1,5 +1,6 @@ { "$schema": "https://www.schemastore.org/jsconfig.json", + "include": ["src/**/*", "types/**/*"], "compilerOptions": { "module": "commonjs", "skipDefaultLibCheck": true, diff --git a/package.json b/package.json index c4ae313..f897c7f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,12 @@ { - "dependencies": {}, + "scripts": { + "generate-types": "node scripts/AutoExport.js" + }, "devDependencies": { + "@babel/generator": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6", "oxlint": "^1.39.0" } -} \ No newline at end of file +} diff --git a/scripts/AutoExport.js b/scripts/AutoExport.js new file mode 100644 index 0000000..2b25c1f --- /dev/null +++ b/scripts/AutoExport.js @@ -0,0 +1,453 @@ +#!/usr/bin/env node + +const fs = require("fs"); +const path = require("path"); +const { default: traverse } = require("@babel/traverse"); +const parser = require("@babel/parser"); +const t = require("@babel/types"); + +/** + * Recursively find all .js files in a directory + * @param {string} dir - Directory path + * @param {string[]} files - Array to store found files + * @returns {string[]} + */ +function findJsFiles(dir, files = []) { + const entries = fs.readdirSync(dir, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + + if (entry.isDirectory()) { + findJsFiles(fullPath, files); + } else if (entry.isFile() && entry.name.endsWith(".js")) { + files.push(fullPath); + } + } + + return files; +} + +/** + * Extract JSDoc comment from a node + * @param {import('@babel/types').Node} node + * @returns {string | null} + */ +function extractJSDoc(node) { + if (node.leadingComments && node.leadingComments.length > 0) { + const comment = node.leadingComments[node.leadingComments.length - 1]; + if (comment.type === "CommentBlock" && comment.value.startsWith("*")) { + return comment.value; + } + } + return null; +} + +/** + * Parse @type annotation from JSDoc + * @param {string | null} jsdoc + * @returns {string | null} + */ +function parseTypeFromJSDoc(jsdoc) { + if (!jsdoc) { + return null; + } + + const typeIndex = jsdoc.indexOf("@type"); + if (typeIndex === -1) return null; + + const openBraceIndex = jsdoc.indexOf("{", typeIndex); + if (openBraceIndex === -1) return null; + + // Use stack to find matching closing brace + let stack = 0; + let closeBraceIndex = -1; + + for (let i = openBraceIndex; i < jsdoc.length; i++) { + const char = jsdoc[i]; + if (char === "{") { + stack++; + } else if (char === "}") { + stack--; + if (stack === 0) { + closeBraceIndex = i; + break; + } + } + } + + if (closeBraceIndex === -1) return null; + + // Extract type between braces + const typeContent = jsdoc.substring(openBraceIndex + 1, closeBraceIndex); + + // Remove JSDoc comment markers and extra whitespace + return formatJSDocType(typeContent); +} + +/** + * Parse function type from JSDoc + * @param {string | null} jsdoc + * @returns {string | null} + */ +function parseFunctionTypeFromJSDoc(jsdoc) { + if (!jsdoc) { + return null; + } + + // Extract @param tags + const paramMatches = [...jsdoc.matchAll(/@param\s+\{([^}]+)\}\s+(\w+)/g)]; + + // Extract @returns tag + const returnMatch = jsdoc.match(/@returns\s+\{([^}]+)\}/); + + if (paramMatches.length === 0 && !returnMatch) { + return null; + } + + // Format parameters and return types + const params = paramMatches.map((m) => { + const paramName = m[2]; + const paramType = formatJSDocType(m[1]); + return `${paramName}: ${paramType}`; + }).join(", "); + + const returnType = returnMatch ? formatJSDocType(returnMatch[1]) : "void"; + + return `(${params}) => ${returnType}`; +} + +/** + * Infer type from value expression + * @param {import('@babel/types').Node} node + * @returns {string} + */ +function inferTypeFromExpression(node) { + if (t.isNumericLiteral(node)) return "number"; + if (t.isStringLiteral(node)) return "string"; + if (t.isBooleanLiteral(node)) return "boolean"; + + // Handle arithmetic expressions (result is number) + if (t.isBinaryExpression(node)) { + return "number"; + } + + if (t.isArrayExpression(node)) return "any[]"; + if (t.isObjectExpression(node)) return "{ [key: string]: any }"; + if (t.isFunctionExpression(node) || t.isArrowFunctionExpression(node)) return "any => any"; + if (t.isIdentifier(node)) return "any"; + + return "any"; +} + +/** + * Format multi-line JSDoc type to single line + * @param {string} type + * @returns {string} + */ +function formatJSDocType(type) { + if (!type) return type; + + // Replace newlines and extra whitespace + let formatted = type + .split("\n") + .map(line => line.trim()) + .filter(line => line.length > 0) + .join(" "); + + // Remove JSDoc comment markers (*) + formatted = formatted.replace(/\s*\*\s*/g, " "); + + // Clean up extra spaces + formatted = formatted.replace(/\s+/g, " "); + + return formatted; +} + +/** + * Extract JSDoc from statement before export call + * @param {import('@babel/traverse').NodePath} path + * @returns {string | null} + */ +function findJSDocForExport(path) { + // Get the call expression's statement parent + const statementPath = path.getStatementParent(); + if (!statementPath) return null; + + const { node } = statementPath; + + // Check if there's a standalone JSDoc comment before statement + if (node.leadingComments && node.leadingComments.length > 0) { + for (let i = node.leadingComments.length - 1; i >= 0; i--) { + const comment = node.leadingComments[i]; + if (comment.type === "CommentBlock" && comment.value.includes("@type")) { + return comment.value; + } + } + } + + return null; +} + +/** + * Collect all exports from a file + * @param {string} filePath + * @returns {{ name: string, type: string, sourceFile: string }[]} + */ +function collectExportsFromFile(filePath) { + const content = fs.readFileSync(filePath, "utf-8"); + const exports = []; + + try { + const ast = parser.parse(content, { + sourceType: "module", + plugins: [], + }); + + // First pass: collect all variable declarations with their types + const variableTypes = new Map(); + + traverse(ast, { + VariableDeclarator(path) { + const { node } = path; + if (t.isIdentifier(node.id)) { + const varName = node.id.name; + + // Try to get JSDoc from VariableDeclaration parent + let jsdoc = extractJSDoc(path.parent); + + // If no JSDoc on parent, try on declarator + if (!jsdoc) { + jsdoc = extractJSDoc(node); + } + + let type = parseTypeFromJSDoc(jsdoc) ?? parseFunctionTypeFromJSDoc(jsdoc) ?? inferTypeFromExpression(node.init); + + if (type !== "any") { + variableTypes.set(varName, type); + } + } + }, + + FunctionDeclaration(path) { + const { node } = path; + if (t.isIdentifier(node.id)) { + const funcName = node.id.name; + const jsdoc = extractJSDoc(node); + const type = parseFunctionTypeFromJSDoc(jsdoc); + + if (type && type !== "any") { + variableTypes.set(funcName, type); + } + } + }, + }); + + // Second pass: find exports + traverse(ast, { + /** + * Detect: global["name"] = value + */ + AssignmentExpression(babelPath) { + const { node } = babelPath; + const left = node.left; + + if ( + t.isMemberExpression(left) && + t.isIdentifier(left.object, { name: "global" }) && + t.isStringLiteral(left.property) + ) { + const exportName = left.property.value; + const jsdoc = extractJSDoc(node); + + let type = parseTypeFromJSDoc(jsdoc); + + // Try to get type from value identifier + if (!type && t.isIdentifier(node.right)) { + type = variableTypes.get(node.right.name); + } + + if (!type) { + type = inferTypeFromExpression(node.right); + } + + // Format type to single line + type = formatJSDocType(type); + + exports.push({ + name: exportName, + type, + sourceFile: path.relative("src", filePath), + }); + } + }, + + /** + * Detect: dataBus.export("name", value) + */ + CallExpression(babelPath) { + const { node } = babelPath; + + if ( + t.isMemberExpression(node.callee) && + t.isIdentifier(node.callee.object, { name: "dataBus" }) && + t.isIdentifier(node.callee.property, { name: "export" }) && + node.arguments.length >= 2 && + t.isStringLiteral(node.arguments[0]) + ) { + const exportName = node.arguments[0].value; + const valueArg = node.arguments[1]; + + // Check for JSDoc on export statement + let type = parseTypeFromJSDoc(findJSDocForExport(babelPath)); + + // Try to get type from value identifier + if (!type && t.isIdentifier(valueArg)) { + type = variableTypes.get(valueArg.name); + } + + if (!type) { + type = inferTypeFromExpression(valueArg); + } + + // Format type to single line + type = formatJSDocType(type); + + exports.push({ + name: exportName, + type, + sourceFile: path.relative("src", filePath), + }); + } + }, + }); + } catch (error) { + console.error(`Error parsing ${filePath}:`, error.message); + } + + return exports; +} + +/** + * Generate TypeScript declaration file + * @param {{ name: string, type: string, sourceFile: string }[]} allExports + * @param {string} outputPath + */ +function generateDTS(allExports, outputPath) { + const uniqueExports = new Map(); + + for (const exp of allExports) { + if (!uniqueExports.has(exp.name)) { + uniqueExports.set(exp.name, exp); + } + } + + const sortedExports = Array.from(uniqueExports.values()).sort((a, b) => a.name.localeCompare(b.name)); + + let dtsContent = `/** + * Auto-generated DataBus type declarations + * Generated by scripts/AutoExport.js + * Do not edit manually + */ + +declare global { + /** + * All exported data types + */ + interface ExportTypes { +`; + + for (const exp of sortedExports) { + dtsContent += ` /**\n`; + dtsContent += ` * Source: ${exp.sourceFile}\n`; + dtsContent += ` * Type: ${exp.type}\n`; + dtsContent += ` */\n`; + dtsContent += ` "${exp.name}": ${exp.type};\n`; + } + + dtsContent += ` }\n\n`; + + dtsContent += ` /**\n`; + dtsContent += ` * DataBus interface with type-safe import\n`; + dtsContent += ` */\n`; + dtsContent += ` interface DataBus {\n`; + dtsContent += ` /**\n`; + dtsContent += ` * Export a value\n`; + dtsContent += ` * @template T\n`; + dtsContent += ` * @param {string} name - Export identifier\n`; + dtsContent += ` * @param {T} value - Value to export\n`; + dtsContent += ` */\n`; + dtsContent += ` export(name: string, value: T): void;\n\n`; + + dtsContent += ` /**\n`; + dtsContent += ` * Import a previously exported value\n`; + dtsContent += ` * @template T\n`; + dtsContent += ` * @param {string} name - Export identifier\n`; + dtsContent += ` * @returns {T} The exported value\n`; + dtsContent += ` */\n`; + dtsContent += ` import(name: T): ExportTypes[T];\n\n`; + + dtsContent += ` /**\n`; + dtsContent += ` * Check if an export exists\n`; + dtsContent += ` * @param {string} name - Export identifier\n`; + dtsContent += ` */\n`; + dtsContent += ` hasExport(name: string): boolean;\n\n`; + + dtsContent += ` /**\n`; + dtsContent += ` * List all available export names\n`; + dtsContent += ` * @returns {string[]}\n`; + dtsContent += ` */\n`; + dtsContent += ` listExports(): string[];\n`; + dtsContent += ` }\n`; + + dtsContent += `}\n\n`; + + dtsContent += `export {};\n`; + + const outputDir = path.dirname(outputPath); + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); + } + + fs.writeFileSync(outputPath, dtsContent, "utf-8"); + console.log(`Generated ${outputPath} with ${sortedExports.length} exports`); + + // Print exports with types + console.log("\nExports found:"); + for (const exp of sortedExports) { + console.log(` ${exp.name}: ${exp.type} (from ${exp.sourceFile})`); + } +} + +/** + * Main function + */ +function main() { + console.log("Scanning src/ directory for exports..."); + + const srcDir = path.join(process.cwd(), "src"); + const jsFiles = findJsFiles(srcDir); + + console.log(`Found ${jsFiles.length} JavaScript files`); + + const allExports = []; + + for (const file of jsFiles) { + const exports = collectExportsFromFile(file); + allExports.push(...exports); + } + + console.log(`Found ${allExports.length} total exports`); + + if (allExports.length === 0) { + console.warn("No exports found. Make sure to use global['name'] = value or dataBus.export('name', value)"); + return; + } + + const outputPath = path.join(process.cwd(), "types", "DataBus.d.ts"); + generateDTS(allExports, outputPath); + + console.log("\nDone!"); +} + +main(); diff --git a/src/server_scripts/C4.js b/src/server_scripts/C4.js index 0a6d135..2f3560f 100644 --- a/src/server_scripts/C4.js +++ b/src/server_scripts/C4.js @@ -4,6 +4,11 @@ * Handles C4 use started and activated events */ +/** + * @type {DataBus} + */ +const dataBus = global["dataBus"]; + // ==================== Block Break Event Handler ==================== BlockEvents.broken((event) => { @@ -15,8 +20,7 @@ BlockEvents.broken((event) => { } // Get the toExplosionC4Map from global - /** @type {{[key:string]: boolean | null}} */ - const toExplosionC4Map = /** @type {any} */ (global["toExplosionC4Map"]); + const toExplosionC4Map = dataBus.import("toExplosionC4Map"); if (toExplosionC4Map === undefined || toExplosionC4Map === null) { console.warn("C4 Server: toExplosionC4Map is not available"); diff --git a/src/startup_scripts/C4.js b/src/startup_scripts/C4.js index 1000fba..c5beaf2 100644 --- a/src/startup_scripts/C4.js +++ b/src/startup_scripts/C4.js @@ -2,14 +2,35 @@ const $TickEvent$PlayerTickEvent = Java.loadClass( "net.minecraftforge.event.TickEvent$PlayerTickEvent", ); -const C4_EXPLOSION_TIME = 10 * 20; // 7 seconds in ticks -const C4_EXPLOSION_POWER = 128; // Explosion power (TNT is 4) -const C4_USE_TIME = 5 * 20; // 5 seconds in ticks +/** + * C4 explosion time in ticks (7 seconds) + * @type {number} + */ +const C4_EXPLOSION_TIME = 10 * 20; + +/** + * C4 explosion power (TNT is 4) + * @type {number} + */ +const C4_EXPLOSION_POWER = 128; + +/** + * C4 use time in ticks (5 seconds) + * @type {number} + */ +const C4_USE_TIME = 5 * 20; + +/** + * @type {DataBus} + */ +const dataBus = /** @type {any} */ (global["dataBus"]); // Export constants for server scripts -global["C4_EXPLOSION_TIME"] = C4_EXPLOSION_TIME; -global["C4_EXPLOSION_POWER"] = C4_EXPLOSION_POWER; -global["C4_USE_TIME"] = C4_USE_TIME; +dataBus.export("C4_EXPLOSION_TIME", C4_EXPLOSION_TIME); + +dataBus.export("C4_EXPLOSION_POWER", C4_EXPLOSION_POWER); + +dataBus.export("C4_USE_TIME", C4_USE_TIME); // Tolerance for floating point comparison const ANGLE_TOLERANCE = 0.001; @@ -30,13 +51,24 @@ let operationKeyMapping; const lastPlayerInfoMap = {}; // Export for server scripts -global["lastPlayerInfoMap"] = lastPlayerInfoMap; +/** + * @type {{ [key: string]:{ + * angle: {x: number, y:number, z:number}, + * pos: {x: number, y: number, z: number}, + * blockPos: {x: number, y: number, z: number} + * } | undefined}} + */ +dataBus.export("lastPlayerInfoMap", lastPlayerInfoMap); /** - * @type {{[key:string]: boolean}} + * @type {{[key:string]: boolean | null}} */ const toExplosionC4Map = {}; -global["toExplosionC4Map"] = toExplosionC4Map; + +/** + * @type {{[key:string]: boolean | null}} + */ +dataBus.export("toExplosionC4Map", toExplosionC4Map); /** * Helper function to compare floating point numbers with tolerance @@ -119,9 +151,16 @@ function shouldActivateC4(itemstack, level, player) { } // Export for server scripts -global["shouldActivateC4"] = shouldActivateC4; +/** + * @param {Internal.ItemStack} itemstack + * @param {Internal.Level} level + * @param {Internal.Player} player + * @returns {boolean} + */ +dataBus.export("shouldActivateC4", shouldActivateC4); /** + * Check if C4 use should start * @param {Internal.Player} player * @param {Internal.Level} level * @returns {boolean} @@ -158,7 +197,12 @@ function shouldStartUseC4(player, level) { } // Export for server scripts -global["shouldStartUseC4"] = shouldStartUseC4; +/** + * @param {Internal.Player} player + * @param {Internal.Level} level + * @returns {boolean} + */ +dataBus.export("shouldStartUseC4", shouldStartUseC4); // ==================== Block Registration ==================== diff --git a/src/startup_scripts/DataBus.js b/src/startup_scripts/DataBus.js index e69de29..692a013 100644 --- a/src/startup_scripts/DataBus.js +++ b/src/startup_scripts/DataBus.js @@ -0,0 +1,62 @@ +/** + * Creates a simple data-exchange bus for sharing values across KubeJS scripts. + * Provides export/import functionality similar to TypeScript modules with type hints. + * + * @returns {DataBus} + */ +function createDataBus() { + /** + * @type {Map} + */ + const dataMap = new Map(); + + /** + * @type {DataBus} + */ + const bus = { + /** + * Export a value under a given name. + * @template T + * @param {string} name - Export identifier + * @param {T} value - Value to export + */ + export: function (name, value) { + dataMap.set(name, value); + }, + + /** + * Import a previously exported value. + * @template T + * @param {string} name - Export identifier + * @returns {T} The exported value + * @throws {Error} If the export does not exist. + */ + import: function (name) { + if (!dataMap.has(name)) { + throw new Error(`DataBus: export "${name}" not found`); + } + return dataMap.get(name); + }, + + /** + * Check if an export exists. + * @param {string} name - Export identifier + * @returns {boolean} + */ + hasExport: function (name) { + return dataMap.has(name); + }, + + /** + * List all available export names. + * @returns {string[]} + */ + listExports: function () { + return Array.from(dataMap.keys()); + }, + }; + + return bus; +} + +global["dataBus"] = createDataBus(); diff --git a/types/DataBus.d.ts b/types/DataBus.d.ts new file mode 100644 index 0000000..07cfe22 --- /dev/null +++ b/types/DataBus.d.ts @@ -0,0 +1,93 @@ +/** + * Auto-generated DataBus type declarations + * Generated by scripts/AutoExport.js + * Do not edit manually + */ + +declare global { + /** + * All exported data types + */ + interface ExportTypes { + /** + * Source: startup_scripts\C4.js + * Type: number + */ + "C4_EXPLOSION_POWER": number; + /** + * Source: startup_scripts\C4.js + * Type: number + */ + "C4_EXPLOSION_TIME": number; + /** + * Source: startup_scripts\C4.js + * Type: number + */ + "C4_USE_TIME": number; + /** + * Source: startup_scripts\DataBus.js + * Type: any + */ + "dataBus": any; + /** + * Source: startup_scripts\EventBus.js + * Type: any + */ + "eventBus": any; + /** + * Source: startup_scripts\C4.js + * Type: { [key: string]:{ angle: {x: number, y:number, z:number}, pos: {x: number, y: number, z: number}, blockPos: {x: number, y: number, z: number} } | undefined} + */ + "lastPlayerInfoMap": { [key: string]:{ angle: {x: number, y:number, z:number}, pos: {x: number, y: number, z: number}, blockPos: {x: number, y: number, z: number} } | undefined}; + /** + * Source: startup_scripts\C4.js + * Type: (itemstack: Internal.ItemStack, level: Internal.Level, player: Internal.Player) => boolean + */ + "shouldActivateC4": (itemstack: Internal.ItemStack, level: Internal.Level, player: Internal.Player) => boolean; + /** + * Source: startup_scripts\C4.js + * Type: (player: Internal.Player, level: Internal.Level) => boolean + */ + "shouldStartUseC4": (player: Internal.Player, level: Internal.Level) => boolean; + /** + * Source: startup_scripts\C4.js + * Type: {[key:string]: boolean | null} + */ + "toExplosionC4Map": {[key:string]: boolean | null}; + } + + /** + * DataBus interface with type-safe import + */ + interface DataBus { + /** + * Export a value + * @template T + * @param {string} name - Export identifier + * @param {T} value - Value to export + */ + export(name: string, value: T): void; + + /** + * Import a previously exported value + * @template T + * @param {string} name - Export identifier + * @returns {T} The exported value + */ + import(name: T): ExportTypes[T]; + + /** + * Check if an export exists + * @param {string} name - Export identifier + */ + hasExport(name: string): boolean; + + /** + * List all available export names + * @returns {string[]} + */ + listExports(): string[]; + } +} + +export {};