mirror of
https://github.com/SikongJueluo/kubejs-utils.git
synced 2026-01-29 08:17:49 +08:00
Compare commits
15 Commits
ffd045daa4
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
6178a82d24
|
|||
|
9d65768cd3
|
|||
|
1e27eb7e8c
|
|||
|
f996cfff58
|
|||
|
7586b628f0
|
|||
|
498022de1d
|
|||
|
670b7e14a8
|
|||
|
8ffa4daa60
|
|||
|
c494b5fb3e
|
|||
| 39865e058d | |||
| f7d8b7b7b0 | |||
| c208dd77ca | |||
| 047d094341 | |||
| b15ffafcd4 | |||
| 3e0d55c94f |
18
.envrc
18
.envrc
@@ -1,9 +1,9 @@
|
|||||||
#!/bin/sh
|
#!/bin/bash
|
||||||
|
|
||||||
if [ -f ".env" ]; then
|
if [ -f ".env" ]; then
|
||||||
export $(dotenvx get --format=shell -f .env)
|
export $(dotenvx get --format=shell -f .env)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -f ".env.local" ]; then
|
if [ -f ".env.local" ]; then
|
||||||
export $(dotenvx get --format=shell -f .env.local)
|
export $(dotenvx get --format=shell -f .env.local)
|
||||||
fi
|
fi
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,3 +5,4 @@ output
|
|||||||
.ai
|
.ai
|
||||||
.env.local
|
.env.local
|
||||||
logs
|
logs
|
||||||
|
types/probe
|
||||||
|
|||||||
11
.justfile
11
.justfile
@@ -1,5 +1,12 @@
|
|||||||
|
log_dir := if os() == "linux" { "$LOG_DIR" } else { "$(cygpath -u $LOG_DIR)/" }
|
||||||
|
kjs_dir := if os() == "linux" { "$KJS_DIR" } else { "$(cygpath -u $KJS_DIR)/" }
|
||||||
|
pjs_dir := if os() == "linux" { "$PJS_DIR" } else { "$(cygpath -u $PJS_DIR)/" }
|
||||||
|
|
||||||
sync-js:
|
sync-js:
|
||||||
rsync -av --delete -r --exclude=config --exclude=probe ./src/ "$(cygpath -u $DST_KJS_DIR)/"
|
rsync -av --delete -r --exclude=config --exclude=probe --exclude=*.txt ./src/ {{ kjs_dir }}
|
||||||
|
|
||||||
sync-logs:
|
sync-logs:
|
||||||
rsync -av --delete -r "$(cygpath -u $DST_LOG_DIR)/" ./logs/
|
ln -s {{ log_dir }} ./logs
|
||||||
|
|
||||||
|
sync-types:
|
||||||
|
ln -s {{ pjs_dir }} ./types/probe
|
||||||
|
|||||||
70
bun.lock
Normal file
70
bun.lock
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"configVersion": 1,
|
||||||
|
"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=="],
|
||||||
|
|
||||||
|
"@oxlint/linux-arm64-gnu": ["@oxlint/linux-arm64-gnu@1.39.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-qocBkvS2V6rH0t9AT3DfQunMnj3xkM7srs5/Ycj2j5ZqMoaWd/FxHNVJDFP++35roKSvsRJoS0mtA8/77jqm6Q=="],
|
||||||
|
|
||||||
|
"@oxlint/linux-arm64-musl": ["@oxlint/linux-arm64-musl@1.39.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-arZzAc1PPcz9epvGBBCMHICeyQloKtHX3eoOe62B3Dskn7gf6Q14wnDHr1r9Vp4vtcBATNq6HlKV14smdlC/qA=="],
|
||||||
|
|
||||||
|
"@oxlint/linux-x64-gnu": ["@oxlint/linux-x64-gnu@1.39.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ZVt5qsECpuNprdWxAPpDBwoixr1VTcZ4qAEQA2l/wmFyVPDYFD3oBY/SWACNnWBddMrswjTg9O8ALxYWoEpmXw=="],
|
||||||
|
|
||||||
|
"@oxlint/linux-x64-musl": ["@oxlint/linux-x64-musl@1.39.0", "", { "os": "linux", "cpu": "x64" }, "sha512-pB0hlGyKPbxr9NMIV783lD6cWL3MpaqnZRM9MWni4yBdHPTKyFNYdg5hGD0Bwg+UP4S2rOevq/+OO9x9Bi7E6g=="],
|
||||||
|
|
||||||
|
"@oxlint/win32-arm64": ["@oxlint/win32-arm64@1.39.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-Gg2SFaJohI9+tIQVKXlPw3FsPQFi/eCSWiCgwPtPn5uzQxHRTeQEZKuluz1fuzR5U70TXubb2liZi4Dgl8LJQA=="],
|
||||||
|
|
||||||
|
"@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=="],
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,26 +1,17 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://www.schemastore.org/jsconfig.json",
|
"$schema": "https://www.schemastore.org/jsconfig.json",
|
||||||
|
"include": ["src/**/*", "types/**/*"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"skipDefaultLibCheck": true,
|
||||||
|
"moduleResolution": "classic",
|
||||||
|
"composite": true,
|
||||||
|
"isolatedModules": true,
|
||||||
"lib": ["ES5", "ES2015"],
|
"lib": ["ES5", "ES2015"],
|
||||||
|
"incremental": true,
|
||||||
"target": "ES2015",
|
"target": "ES2015",
|
||||||
"strict": true,
|
|
||||||
"checkJs": true,
|
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"noImplicitAny": true,
|
"rootDir": ".",
|
||||||
"esModuleInterop": true,
|
"allowJs": true
|
||||||
"moduleResolution": "bundler",
|
}
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"rootDirs": [
|
|
||||||
"src/server_scripts",
|
|
||||||
"src/startup_scripts",
|
|
||||||
"src/client_scripts",
|
|
||||||
"src/lib"
|
|
||||||
],
|
|
||||||
"typeRoots": [
|
|
||||||
"./types/probe/user",
|
|
||||||
"./types/probe/generated",
|
|
||||||
"./types/probe/generated/internals"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"include": ["./src/**/*.js", "./types/**/*.d.ts"]
|
|
||||||
}
|
}
|
||||||
|
|||||||
12
package.json
Normal file
12
package.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
453
scripts/AutoExport.js
Normal file
453
scripts/AutoExport.js
Normal file
@@ -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<T>(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<T extends keyof ExportTypes>(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();
|
||||||
6
src/assets/kubejs/lang/en_us.json
Normal file
6
src/assets/kubejs/lang/en_us.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"block.kubejs.c4_target": "C4 Target Block",
|
||||||
|
"block.kubejs.c4": "C4",
|
||||||
|
"item.kubejs.c4_item": "C4",
|
||||||
|
"item.kubejs.c4_defuser": "C4 Defuser"
|
||||||
|
}
|
||||||
6
src/assets/kubejs/lang/zh_cn.json
Normal file
6
src/assets/kubejs/lang/zh_cn.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"block.kubejs.c4_target": "C4 Target Block",
|
||||||
|
"block.kubejs.c4": "C4",
|
||||||
|
"item.kubejs.c4_item": "C4",
|
||||||
|
"item.kubejs.c4_defuser": "C4 Defuser"
|
||||||
|
}
|
||||||
15
src/client_scripts/ProbeDev.js
Normal file
15
src/client_scripts/ProbeDev.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* WARN: Need Probejs v7.0
|
||||||
|
*
|
||||||
|
* NOTE: This file is used for development. There is no need to load.
|
||||||
|
* Events execute when ProbeJS dumps.
|
||||||
|
*
|
||||||
|
* Author: SikongJueluo
|
||||||
|
* License: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
ProbeEvents.assignType((event) => {});
|
||||||
|
|
||||||
|
ProbeEvents.modifyClass((event) => {});
|
||||||
|
|
||||||
|
ProbeEvents.snippets((event) => {});
|
||||||
18
src/client_scripts/Test.js
Normal file
18
src/client_scripts/Test.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
let isFirstTime = true;
|
||||||
|
ClientEvents.tick((event) => {
|
||||||
|
if (!isFirstTime) return;
|
||||||
|
|
||||||
|
const player = event.player;
|
||||||
|
const lookAngle = player.lookAngle;
|
||||||
|
console.log(`Player Pos: ${player.x}, ${player.y}, ${player.z}`);
|
||||||
|
// console.log(
|
||||||
|
// `LookAngle(property): ${lookAngle.x}, ${lookAngle.y}, ${lookAngle.z}`,
|
||||||
|
// );
|
||||||
|
console.log(
|
||||||
|
`LookAngle(method): ${lookAngle.x()}, ${lookAngle.y()}, ${lookAngle.z()}`,
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`LookAngle(get method): ${lookAngle.get("x")}, ${lookAngle.get("y")}, ${lookAngle.get("z")}`,
|
||||||
|
);
|
||||||
|
isFirstTime = false;
|
||||||
|
});
|
||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
19
src/client_scripts/jsconfig.json
Normal file
19
src/client_scripts/jsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://www.schemastore.org/jsconfig.json",
|
||||||
|
"extends": "../../jsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": [
|
||||||
|
"../../types/probe/client/packages",
|
||||||
|
"../../types/probe/client/global",
|
||||||
|
"../../types/probe/shared"
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"packages/*": [
|
||||||
|
"../../types/probe/client/packages/*",
|
||||||
|
"../../types/probe/shared/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["./**/*.js", "../../types/*.d.ts"],
|
||||||
|
"exclude": []
|
||||||
|
}
|
||||||
249
src/server_scripts/C4.js
Normal file
249
src/server_scripts/C4.js
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
/**
|
||||||
|
* C4 Server Scripts
|
||||||
|
* Handles C4 block break event to cancel explosion
|
||||||
|
* Handles C4 use started and activated events
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {DataBus}
|
||||||
|
*/
|
||||||
|
const dataBus = global["dataBus"];
|
||||||
|
|
||||||
|
// ==================== Block Break Event Handler ====================
|
||||||
|
|
||||||
|
BlockEvents.broken((event) => {
|
||||||
|
const { block, server } = event;
|
||||||
|
|
||||||
|
// Check if the broken block is a C4
|
||||||
|
if (block.id !== "kubejs:c4") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the toExplosionC4Map from global
|
||||||
|
const toExplosionC4Map = dataBus.import("toExplosionC4Map");
|
||||||
|
|
||||||
|
if (toExplosionC4Map === undefined || toExplosionC4Map === null) {
|
||||||
|
console.warn("C4 Server: toExplosionC4Map is not available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the block position string
|
||||||
|
const blockPosString = block.pos.toShortString();
|
||||||
|
|
||||||
|
// Check if this C4 is in the explosion map
|
||||||
|
if (toExplosionC4Map[blockPosString] === true) {
|
||||||
|
// Set to null to cancel the explosion
|
||||||
|
// The explosion timer checks for === null to skip explosion
|
||||||
|
toExplosionC4Map[blockPosString] = null;
|
||||||
|
|
||||||
|
// Notify players that C4 has been defused
|
||||||
|
if (server !== null) {
|
||||||
|
server.players.forEach((player) => {
|
||||||
|
player.tell(
|
||||||
|
/** @type {any} */ (
|
||||||
|
Component.literal("§aC4已被拆除,爆炸已取消!")
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.warn("C4 Server: Server is null");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== C4 Event Handlers ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{player: Internal.Player}} event
|
||||||
|
*/
|
||||||
|
function handleC4UseStarted(event) {
|
||||||
|
const server = Utils.getServer();
|
||||||
|
if (server === null) {
|
||||||
|
console.error("C4 Handler: Server is not available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get shared variables from global
|
||||||
|
/** @type {typeof shouldActivateC4} */
|
||||||
|
const shouldActivateC4 = /** @type {any} */ (global["shouldActivateC4"]);
|
||||||
|
/** @type {typeof shouldStartUseC4} */
|
||||||
|
const shouldStartUseC4 = /** @type {any} */ (global["shouldStartUseC4"]);
|
||||||
|
/** @type {{ [key: string]: any }} */
|
||||||
|
const lastPlayerInfoMap = /** @type {any} */ (global["lastPlayerInfoMap"]);
|
||||||
|
/** @type {number} */
|
||||||
|
const C4_USE_TIME = /** @type {any} */ (global["C4_USE_TIME"]);
|
||||||
|
|
||||||
|
if (
|
||||||
|
shouldActivateC4 === undefined ||
|
||||||
|
shouldStartUseC4 === undefined ||
|
||||||
|
lastPlayerInfoMap === undefined ||
|
||||||
|
C4_USE_TIME === undefined
|
||||||
|
) {
|
||||||
|
console.error("C4 Handler: Required global variables not available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const player = server.getPlayerList().getPlayer(event.player.uuid);
|
||||||
|
const level = player.level;
|
||||||
|
|
||||||
|
const startTime = level.levelData.gameTime;
|
||||||
|
const originalItemstack = player.mainHandItem;
|
||||||
|
|
||||||
|
server.scheduleRepeatingInTicks(2, (event) => {
|
||||||
|
const itemstack = player.getMainHandItem();
|
||||||
|
|
||||||
|
if (
|
||||||
|
!shouldActivateC4(
|
||||||
|
itemstack,
|
||||||
|
player.level,
|
||||||
|
/** @type {any} */ (player),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
player.stopUsingItem();
|
||||||
|
player.addItemCooldown(originalItemstack.item, 20);
|
||||||
|
originalItemstack.releaseUsing(
|
||||||
|
level,
|
||||||
|
/** @type {any} */ (player),
|
||||||
|
originalItemstack.count,
|
||||||
|
);
|
||||||
|
event.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get remaining ticks for this use
|
||||||
|
const remainingTicks =
|
||||||
|
C4_USE_TIME - (level.levelData.gameTime - startTime);
|
||||||
|
|
||||||
|
if (remainingTicks <= 0) {
|
||||||
|
originalItemstack.finishUsingItem(
|
||||||
|
level,
|
||||||
|
/** @type {any} */ (player),
|
||||||
|
);
|
||||||
|
delete lastPlayerInfoMap[player.uuid.toString()];
|
||||||
|
event.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
itemstack.setHoverName(
|
||||||
|
/** @type {any} */ (
|
||||||
|
Component.literal(`C4 - ${(remainingTicks / 20.0).toFixed(1)}s`)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle C4 activation event
|
||||||
|
* @param {C4ActivatedEvent} event
|
||||||
|
*/
|
||||||
|
function handleC4Activated(event) {
|
||||||
|
const server = Utils.getServer();
|
||||||
|
if (server === null) {
|
||||||
|
console.error("C4 Handler: Server is not available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { level, player, explosionTime, explosionPower } = event;
|
||||||
|
|
||||||
|
// Get shared variables from global
|
||||||
|
/** @type {{[key:string]: boolean | null}} */
|
||||||
|
const toExplosionC4Map = /** @type {any} */ (global["toExplosionC4Map"]);
|
||||||
|
|
||||||
|
if (toExplosionC4Map === undefined || toExplosionC4Map === null) {
|
||||||
|
console.error("C4 Handler: toExplosionC4Map is not available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Place C4 at player's feet
|
||||||
|
const c4BlockPos = {
|
||||||
|
x: Math.floor(player.x),
|
||||||
|
y: Math.floor(player.y),
|
||||||
|
z: Math.floor(player.z),
|
||||||
|
};
|
||||||
|
const newBlock = level.getBlock(c4BlockPos.x, c4BlockPos.y, c4BlockPos.z);
|
||||||
|
newBlock.set(/** @type {any} */ ("kubejs:c4"));
|
||||||
|
|
||||||
|
// Add record
|
||||||
|
const newBlockPosString = newBlock.pos.toShortString();
|
||||||
|
toExplosionC4Map[newBlockPosString] = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: It should use reschedule to replace several schedules
|
||||||
|
* But reschedule not work at current time.
|
||||||
|
* Relative Issue: https://github.com/KubeJS-Mods/KubeJS/issues/763
|
||||||
|
*/
|
||||||
|
let remainingSeconds = explosionTime / 20;
|
||||||
|
server.scheduleRepeatingInTicks(20, (scheduledEvent) => {
|
||||||
|
// Assert C4 exsiting
|
||||||
|
if (toExplosionC4Map[newBlockPosString] === null) {
|
||||||
|
scheduledEvent.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
remainingSeconds -= 1;
|
||||||
|
if (remainingSeconds <= 0) {
|
||||||
|
scheduledEvent.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
server.players.forEach((p) => {
|
||||||
|
p.tell(
|
||||||
|
/** @type {any} */ (
|
||||||
|
Component.literal(`C4还剩 ${remainingSeconds} 秒爆炸`)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create explosion after countdown
|
||||||
|
server.scheduleInTicks(explosionTime, (_) => {
|
||||||
|
// Assert C4 exsiting
|
||||||
|
if (toExplosionC4Map[newBlockPosString] === null) return;
|
||||||
|
|
||||||
|
// Emit C4 explosion event
|
||||||
|
/** @type {EventBus} */
|
||||||
|
const eventBus = /** @type {any} */ (global["eventBus"]);
|
||||||
|
if (eventBus === null) return;
|
||||||
|
|
||||||
|
eventBus.emit("C4Explosion", {
|
||||||
|
level: level,
|
||||||
|
position: c4BlockPos,
|
||||||
|
power: explosionPower,
|
||||||
|
});
|
||||||
|
|
||||||
|
level.explode(
|
||||||
|
/** @type {any} */ (null),
|
||||||
|
c4BlockPos.x + 0.5,
|
||||||
|
c4BlockPos.y + 0.5,
|
||||||
|
c4BlockPos.z + 0.5,
|
||||||
|
explosionPower,
|
||||||
|
"block",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Server Initialization ====================
|
||||||
|
|
||||||
|
ServerEvents.loaded((event) => {
|
||||||
|
/**
|
||||||
|
* WARNING: Must Do!!!
|
||||||
|
* Because Kubejs scheduler is not stable
|
||||||
|
* And need to fire once at first time
|
||||||
|
* Relative Issue: https://github.com/KubeJS-Mods/KubeJS/issues/763
|
||||||
|
*/
|
||||||
|
event.server.scheduleInTicks(1, (_) => {
|
||||||
|
console.log("Init Scheduler");
|
||||||
|
});
|
||||||
|
|
||||||
|
/** @type {EventBus} */
|
||||||
|
const eventBus = /** @type {any} */ (global["eventBus"]);
|
||||||
|
|
||||||
|
if (eventBus === null) {
|
||||||
|
console.error("C4 Handler: eventBus is not available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
eventBus.register("C4Activated", handleC4Activated);
|
||||||
|
eventBus.register("C4UseStarted", handleC4UseStarted);
|
||||||
|
console.log("C4 Handler: Registered C4Activated event handler");
|
||||||
|
});
|
||||||
0
src/server_scripts/TeamsManager.js
Normal file
0
src/server_scripts/TeamsManager.js
Normal file
19
src/server_scripts/jsconfig.json
Normal file
19
src/server_scripts/jsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://www.schemastore.org/jsconfig.json",
|
||||||
|
"extends": "../../jsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": [
|
||||||
|
"../../types/probe/server/packages",
|
||||||
|
"../../types/probe/server/global",
|
||||||
|
"../../types/probe/shared"
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"packages/*": [
|
||||||
|
"../../types/probe/server/packages/*",
|
||||||
|
"../../types/probe/shared/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["./**/*.js", "../../types/*.d.ts"],
|
||||||
|
"exclude": []
|
||||||
|
}
|
||||||
@@ -1,12 +1,46 @@
|
|||||||
const KeyMapping = Java.loadClass("net.minecraft.client.KeyMapping");
|
const $TickEvent$PlayerTickEvent = Java.loadClass(
|
||||||
|
"net.minecraftforge.event.TickEvent$PlayerTickEvent",
|
||||||
|
);
|
||||||
|
|
||||||
const C4_EXPLOSION_TIME = 3 * 20; // 3 seconds in ticks
|
/**
|
||||||
const C4_EXPLOSION_POWER = 128; // Explosion power (TNT is 4)
|
* 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
|
||||||
|
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
|
// Tolerance for floating point comparison
|
||||||
const ANGLE_TOLERANCE = 0.001;
|
const ANGLE_TOLERANCE = 0.001;
|
||||||
const POS_TOLERANCE = 0.01;
|
const POS_TOLERANCE = 0.01;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Internal.KeyMapping | undefined}
|
||||||
|
*/
|
||||||
|
let operationKeyMapping;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {{ [key: string]:{
|
* @type {{ [key: string]:{
|
||||||
* angle: {x: number, y:number, z:number},
|
* angle: {x: number, y:number, z:number},
|
||||||
@@ -16,6 +50,26 @@ const POS_TOLERANCE = 0.01;
|
|||||||
*/
|
*/
|
||||||
const lastPlayerInfoMap = {};
|
const lastPlayerInfoMap = {};
|
||||||
|
|
||||||
|
// Export for server scripts
|
||||||
|
/**
|
||||||
|
* @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 | null}}
|
||||||
|
*/
|
||||||
|
const toExplosionC4Map = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {{[key:string]: boolean | null}}
|
||||||
|
*/
|
||||||
|
dataBus.export("toExplosionC4Map", toExplosionC4Map);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function to compare floating point numbers with tolerance
|
* Helper function to compare floating point numbers with tolerance
|
||||||
* @param {number} a
|
* @param {number} a
|
||||||
@@ -32,12 +86,11 @@ function isApproximatelyEqual(a, b, tolerance) {
|
|||||||
* @param {Internal.Player} player
|
* @param {Internal.Player} player
|
||||||
* @returns {{x: number, y: number, z: number}}
|
* @returns {{x: number, y: number, z: number}}
|
||||||
*/
|
*/
|
||||||
function getBlockUnderPlayer(player) {
|
function getFeetBlockPosition(player) {
|
||||||
const playerPos = player.position();
|
|
||||||
return {
|
return {
|
||||||
x: Math.floor(playerPos.x()),
|
x: Math.floor(player.x),
|
||||||
y: Math.floor(playerPos.y()) - 1,
|
y: Math.floor(player.y) - 1,
|
||||||
z: Math.floor(playerPos.z()),
|
z: Math.floor(player.z),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,17 +101,16 @@ function getBlockUnderPlayer(player) {
|
|||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
function shouldActivateC4(itemstack, level, player) {
|
function shouldActivateC4(itemstack, level, player) {
|
||||||
const blockUnder = getBlockUnderPlayer(player);
|
const blockUnder = getFeetBlockPosition(player);
|
||||||
const block = level.getBlock(blockUnder.x, blockUnder.y, blockUnder.z);
|
const block = level.getBlock(blockUnder.x, blockUnder.y, blockUnder.z);
|
||||||
|
|
||||||
const lookAngle = player.lookAngle;
|
const lookAngle = player.lookAngle;
|
||||||
const playerPos = player.position();
|
|
||||||
const lastPlayerInfo = lastPlayerInfoMap[player.uuid.toString()];
|
const lastPlayerInfo = lastPlayerInfoMap[player.uuid.toString()];
|
||||||
|
|
||||||
if (lastPlayerInfo === undefined) return false;
|
if (lastPlayerInfo === undefined) return false;
|
||||||
|
|
||||||
// Check if player moved (using block position for stability)
|
// Check if player moved (using block position for stability)
|
||||||
const currentBlockPos = getBlockUnderPlayer(player);
|
const currentBlockPos = getFeetBlockPosition(player);
|
||||||
const isBlockPosChanged =
|
const isBlockPosChanged =
|
||||||
currentBlockPos.x !== lastPlayerInfo.blockPos.x ||
|
currentBlockPos.x !== lastPlayerInfo.blockPos.x ||
|
||||||
currentBlockPos.y !== lastPlayerInfo.blockPos.y ||
|
currentBlockPos.y !== lastPlayerInfo.blockPos.y ||
|
||||||
@@ -66,36 +118,24 @@ function shouldActivateC4(itemstack, level, player) {
|
|||||||
|
|
||||||
// Check if player moved within the same block (with tolerance)
|
// Check if player moved within the same block (with tolerance)
|
||||||
const isPosChanged =
|
const isPosChanged =
|
||||||
!isApproximatelyEqual(
|
!isApproximatelyEqual(player.x, lastPlayerInfo.pos.x, POS_TOLERANCE) ||
|
||||||
playerPos.x(),
|
!isApproximatelyEqual(player.y, lastPlayerInfo.pos.y, POS_TOLERANCE) ||
|
||||||
lastPlayerInfo.pos.x,
|
!isApproximatelyEqual(player.z, lastPlayerInfo.pos.z, POS_TOLERANCE);
|
||||||
POS_TOLERANCE,
|
|
||||||
) ||
|
|
||||||
!isApproximatelyEqual(
|
|
||||||
playerPos.y(),
|
|
||||||
lastPlayerInfo.pos.y,
|
|
||||||
POS_TOLERANCE,
|
|
||||||
) ||
|
|
||||||
!isApproximatelyEqual(
|
|
||||||
playerPos.z(),
|
|
||||||
lastPlayerInfo.pos.z,
|
|
||||||
POS_TOLERANCE,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Check if player rotated view (with tolerance)
|
// Check if player rotated view (with tolerance)
|
||||||
const isAngleChanged =
|
const isAngleChanged =
|
||||||
!isApproximatelyEqual(
|
!isApproximatelyEqual(
|
||||||
lookAngle.x(),
|
lookAngle.get("x"),
|
||||||
lastPlayerInfo.angle.x,
|
lastPlayerInfo.angle.x,
|
||||||
ANGLE_TOLERANCE,
|
ANGLE_TOLERANCE,
|
||||||
) ||
|
) ||
|
||||||
!isApproximatelyEqual(
|
!isApproximatelyEqual(
|
||||||
lookAngle.y(),
|
lookAngle.get("y"),
|
||||||
lastPlayerInfo.angle.y,
|
lastPlayerInfo.angle.y,
|
||||||
ANGLE_TOLERANCE,
|
ANGLE_TOLERANCE,
|
||||||
) ||
|
) ||
|
||||||
!isApproximatelyEqual(
|
!isApproximatelyEqual(
|
||||||
lookAngle.z(),
|
lookAngle.get("z"),
|
||||||
lastPlayerInfo.angle.z,
|
lastPlayerInfo.angle.z,
|
||||||
ANGLE_TOLERANCE,
|
ANGLE_TOLERANCE,
|
||||||
);
|
);
|
||||||
@@ -110,67 +150,101 @@ function shouldActivateC4(itemstack, level, player) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Export for server scripts
|
||||||
|
/**
|
||||||
|
* @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}
|
||||||
|
*/
|
||||||
|
function shouldStartUseC4(player, level) {
|
||||||
|
const blockUnder = getFeetBlockPosition(player);
|
||||||
|
const block = level.getBlock(blockUnder.x, blockUnder.y, blockUnder.z);
|
||||||
|
|
||||||
|
if (block.id !== "kubejs:c4_target") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const playerUuid = player.uuid.toString();
|
||||||
|
if (lastPlayerInfoMap[playerUuid] !== undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lookAngle = player.lookAngle;
|
||||||
|
lastPlayerInfoMap[playerUuid] = {
|
||||||
|
angle: {
|
||||||
|
x: lookAngle.get("x"),
|
||||||
|
y: lookAngle.get("y"),
|
||||||
|
z: lookAngle.get("z"),
|
||||||
|
},
|
||||||
|
pos: {
|
||||||
|
x: player.x,
|
||||||
|
y: player.y,
|
||||||
|
z: player.z,
|
||||||
|
},
|
||||||
|
blockPos: blockUnder,
|
||||||
|
};
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export for server scripts
|
||||||
|
/**
|
||||||
|
* @param {Internal.Player} player
|
||||||
|
* @param {Internal.Level} level
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
dataBus.export("shouldStartUseC4", shouldStartUseC4);
|
||||||
|
|
||||||
|
// ==================== Block Registration ====================
|
||||||
|
|
||||||
StartupEvents.registry("block", (event) => {
|
StartupEvents.registry("block", (event) => {
|
||||||
event
|
event
|
||||||
.create("c4_target") // Create a new block
|
.create("c4_target") // Create a new block
|
||||||
.soundType(SoundType.WOOD) // Set a material (affects the sounds and some properties)
|
.soundType(SoundType.WOOD) // Set a material (affects the sounds and some properties)
|
||||||
.unbreakable()
|
.unbreakable()
|
||||||
.textureAll("minecraft:block/target_top")
|
.textureAll("minecraft:block/target_top");
|
||||||
.displayName(/** @type {any} */ ("C4 Target Block")); // Set a custom name
|
|
||||||
});
|
|
||||||
|
|
||||||
StartupEvents.registry("block", (event) => {
|
|
||||||
event
|
event
|
||||||
.create("c4") // Create a new block
|
.create("c4") // Create a new block
|
||||||
.soundType(SoundType.GRASS) // Set a material (affects the sounds and some properties)
|
.soundType(SoundType.GRASS) // Set a material (affects the sounds and some properties)
|
||||||
.hardness(1) // Set hardness (affects mining time)
|
.hardness(1) // Set hardness (affects mining time)
|
||||||
|
.requiresTool(true)
|
||||||
|
.tagBlock("minecraft:mineable/axe") //can be mined faster with an axe
|
||||||
.resistance(1) // Set resistance (to explosions, etc)
|
.resistance(1) // Set resistance (to explosions, etc)
|
||||||
.noItem() // Player cannot hold or place the item
|
.noItem() // Player cannot hold or place the item
|
||||||
.noDrops()
|
.noDrops()
|
||||||
.noCollision() // Set no hitbox
|
.noCollision() // Set no hitbox
|
||||||
.textureAll("minecraft:block/tnt_top")
|
.textureAll("minecraft:block/tnt_top");
|
||||||
.displayName(/** @type {any} */ ("C4")); // Set a custom name
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ==================== Item Registration ====================
|
||||||
|
|
||||||
StartupEvents.registry("item", (event) => {
|
StartupEvents.registry("item", (event) => {
|
||||||
event
|
event
|
||||||
.create("c4_item")
|
.create("c4_item")
|
||||||
.unstackable()
|
.unstackable()
|
||||||
.useAnimation("eat")
|
.useAnimation("eat")
|
||||||
.useDuration((_itemStack) => 100) // 5 Seconds
|
.useDuration((_itemStack) => C4_USE_TIME)
|
||||||
.use((level, player, _hand) => {
|
.use((level, player, _hand) => {
|
||||||
const blockUnder = getBlockUnderPlayer(player);
|
if (!shouldStartUseC4(player, level)) return false;
|
||||||
const block = level.getBlock(
|
|
||||||
blockUnder.x,
|
|
||||||
blockUnder.y,
|
|
||||||
blockUnder.z,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (block.id !== "kubejs:c4_target") {
|
/** @type {EventBus} */
|
||||||
return false;
|
const eventBus = /** @type {any} */ (global["eventBus"]);
|
||||||
|
if (eventBus !== null) {
|
||||||
|
eventBus.emit("C4UseStarted", { player: player });
|
||||||
|
} else {
|
||||||
|
console.warn("EventBus is not available");
|
||||||
}
|
}
|
||||||
|
|
||||||
const playerUuid = player.uuid.toString();
|
|
||||||
if (lastPlayerInfoMap[playerUuid] !== undefined) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const playerPos = player.position();
|
|
||||||
const lookAngle = player.lookAngle;
|
|
||||||
lastPlayerInfoMap[playerUuid] = {
|
|
||||||
angle: {
|
|
||||||
x: lookAngle.x(),
|
|
||||||
y: lookAngle.y(),
|
|
||||||
z: lookAngle.z(),
|
|
||||||
},
|
|
||||||
pos: {
|
|
||||||
x: playerPos.x(),
|
|
||||||
y: playerPos.y(),
|
|
||||||
z: playerPos.z(),
|
|
||||||
},
|
|
||||||
blockPos: blockUnder,
|
|
||||||
};
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
.finishUsing((itemstack, level, entity) => {
|
.finishUsing((itemstack, level, entity) => {
|
||||||
@@ -188,127 +262,75 @@ StartupEvents.registry("item", (event) => {
|
|||||||
return itemstack; // Do nothing
|
return itemstack; // Do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
// Place C4 at player's feet
|
|
||||||
const playerPos = player.position();
|
|
||||||
const c4BlockPos = {
|
|
||||||
x: Math.floor(playerPos.x()),
|
|
||||||
y: Math.floor(playerPos.y()),
|
|
||||||
z: Math.floor(playerPos.z()),
|
|
||||||
};
|
|
||||||
const newBlock = level.getBlock(
|
|
||||||
c4BlockPos.x,
|
|
||||||
c4BlockPos.y,
|
|
||||||
c4BlockPos.z,
|
|
||||||
);
|
|
||||||
newBlock.set(/** @type {any} */ ("kubejs:c4"));
|
|
||||||
|
|
||||||
itemstack.shrink(1);
|
itemstack.shrink(1);
|
||||||
itemstack.resetHoverName();
|
|
||||||
delete lastPlayerInfoMap[player.uuid.toString()];
|
|
||||||
|
|
||||||
const server = level.server;
|
// Emit custom event to server_scripts for explosion logic
|
||||||
|
/** @type {EventBus} */
|
||||||
for (let i = 0; i < C4_EXPLOSION_TIME; i += 20) {
|
const eventBus = /** @type {any} */ (global["eventBus"]);
|
||||||
server.scheduleInTicks(i, (event) => {
|
if (eventBus !== null) {
|
||||||
server.players.forEach((p) => {
|
eventBus.emit("C4Activated", {
|
||||||
p.tell(
|
level: level,
|
||||||
/** @type {any} */ (
|
player: player,
|
||||||
Component.literal(
|
explosionTime: C4_EXPLOSION_TIME,
|
||||||
`C4还剩 ${(C4_EXPLOSION_TIME - event.timer) / 20} 秒爆炸`,
|
explosionPower: C4_EXPLOSION_POWER,
|
||||||
)
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create explosion
|
|
||||||
server.scheduleInTicks(C4_EXPLOSION_TIME, (_) => {
|
|
||||||
level.explode(
|
|
||||||
/** @type {any} */ (null),
|
|
||||||
newBlock.pos.x,
|
|
||||||
newBlock.pos.y,
|
|
||||||
newBlock.pos.z,
|
|
||||||
C4_EXPLOSION_POWER,
|
|
||||||
"block",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return itemstack;
|
return itemstack;
|
||||||
})
|
})
|
||||||
.releaseUsing((itemstack, _level, entity, _count) => {
|
.releaseUsing((itemstack, _level, entity, _count) => {
|
||||||
itemstack.resetHoverName();
|
itemstack.resetHoverName();
|
||||||
if (!entity.isPlayer() || entity.uuid === undefined) return;
|
if (!entity.isPlayer() || entity.uuid === undefined) return;
|
||||||
delete lastPlayerInfoMap[entity.uuid.toString()];
|
delete lastPlayerInfoMap[entity.uuid.toString()];
|
||||||
})
|
});
|
||||||
.displayName(/** @type {any} */ ("C4"));
|
|
||||||
|
event.create("c4_defuser", "axe").attackDamageBaseline(0).maxDamage(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
let useItemTickCnt = 0;
|
// ==================== Client Side Logic ====================
|
||||||
const useItemTickInterval = 5;
|
|
||||||
ForgeEvents.onEvent(
|
// Register keybindings during client initialization
|
||||||
"net.minecraftforge.event.entity.living.LivingEntityUseItemEvent$Tick",
|
ClientEvents.init(() => {
|
||||||
(event) => {
|
// Load required Java classes
|
||||||
// Check every 5 ticks (0.25s)
|
const KeyMappingRegistry = Java.loadClass(
|
||||||
if (useItemTickCnt++ % useItemTickInterval !== 0) {
|
"dev.architectury.registry.client.keymappings.KeyMappingRegistry",
|
||||||
return;
|
);
|
||||||
|
const KeyMapping = Java.loadClass("net.minecraft.client.KeyMapping");
|
||||||
|
const GLFW = Java.loadClass("org.lwjgl.glfw.GLFW");
|
||||||
|
|
||||||
|
// Create the KeyMapping
|
||||||
|
// Parameters:
|
||||||
|
// 1. Translation key for the key name (shown in controls menu)
|
||||||
|
// 2. GLFW key code (default key)
|
||||||
|
// 3. Translation key for the category
|
||||||
|
operationKeyMapping = new KeyMapping(
|
||||||
|
"key.kubejs.example", // Will be localized using this translation key
|
||||||
|
GLFW.GLFW_KEY_G, // Default key is G
|
||||||
|
"key.categories.kubejs", // Custom category for KubeJS keybindings
|
||||||
|
);
|
||||||
|
|
||||||
|
// Register the KeyMapping using Architectury's registry
|
||||||
|
KeyMappingRegistry.register(operationKeyMapping);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send data to the server when the key is pressed
|
||||||
|
ForgeEvents.onEvent($TickEvent$PlayerTickEvent, (event) => {
|
||||||
|
if (operationKeyMapping === undefined) {
|
||||||
|
console.warn("Not in client platform");
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (operationKeyMapping.consumeClick()) {
|
||||||
|
const player = event.player;
|
||||||
|
const level = player.level;
|
||||||
|
if (!shouldStartUseC4(player, level)) continue;
|
||||||
|
|
||||||
|
/** @type {EventBus} */
|
||||||
|
const eventBus = /** @type {any} */ (global["eventBus"]);
|
||||||
|
if (eventBus !== null) {
|
||||||
|
eventBus.emit("C4UseStarted", { player: player });
|
||||||
|
} else {
|
||||||
|
console.warn("EventBus is not available");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
const itemstack = event.item;
|
});
|
||||||
if (
|
|
||||||
!event.entity.isPlayer() ||
|
|
||||||
event.entity.uuid === undefined ||
|
|
||||||
itemstack === undefined ||
|
|
||||||
itemstack.id !== "kubejs:c4_item"
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const player = event.entity.level.getPlayerByUUID(event.entity.uuid);
|
|
||||||
|
|
||||||
if (!shouldActivateC4(itemstack, player.level, player)) {
|
|
||||||
player.stopUsingItem();
|
|
||||||
player.addItemCooldown(itemstack.item, 20);
|
|
||||||
itemstack.resetHoverName();
|
|
||||||
delete lastPlayerInfoMap[player.uuid.toString()];
|
|
||||||
event.setCanceled(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get remaining ticks for this use
|
|
||||||
const remainingTicks = event.duration;
|
|
||||||
|
|
||||||
itemstack.setHoverName(
|
|
||||||
/** @type {any} */ (
|
|
||||||
Component.literal(`C4 - ${(remainingTicks / 20.0).toFixed(2)}s`)
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const EXAMPLE_MAPPING = new KeyMapping(
|
|
||||||
"key.examplemod.example1", // Will be localized using this translation key
|
|
||||||
69, // Default key is E
|
|
||||||
"key.categories.misc", // Mapping will be in the misc category
|
|
||||||
);
|
|
||||||
ForgeEvents.onEvent(
|
|
||||||
"net.minecraftforge.client.event.RegisterKeyMappingsEvent",
|
|
||||||
(event) => {
|
|
||||||
event.register(EXAMPLE_MAPPING);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* WARNING: Must Do!!!
|
|
||||||
* Because Kubejs scheduler is not stable
|
|
||||||
* And need to fire once at first time
|
|
||||||
* Relative Issue: https://github.com/KubeJS-Mods/KubeJS/issues/763
|
|
||||||
*/
|
|
||||||
ForgeEvents.onEvent(
|
|
||||||
"net.minecraftforge.event.server.ServerStartedEvent",
|
|
||||||
(event) => {
|
|
||||||
event.server.scheduleInTicks(1, (_) => {
|
|
||||||
console.log("Init Scheduler");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|||||||
62
src/startup_scripts/DataBus.js
Normal file
62
src/startup_scripts/DataBus.js
Normal file
@@ -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<string, any>}
|
||||||
|
*/
|
||||||
|
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();
|
||||||
43
src/startup_scripts/EventBus.js
Normal file
43
src/startup_scripts/EventBus.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* Create a simple event bus for handling custom events in KubeJS environment.
|
||||||
|
* @returns {EventBus}
|
||||||
|
*/
|
||||||
|
function createEventBus() {
|
||||||
|
/**
|
||||||
|
* @type {EventBus}
|
||||||
|
*/
|
||||||
|
const bus = {
|
||||||
|
eventMap: {},
|
||||||
|
|
||||||
|
register: function (eventName, callback) {
|
||||||
|
this.eventMap[eventName] = callback;
|
||||||
|
},
|
||||||
|
|
||||||
|
emit: function (eventName, event) {
|
||||||
|
const callback = this.eventMap[eventName];
|
||||||
|
if (callback) {
|
||||||
|
return callback(event);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return bus;
|
||||||
|
}
|
||||||
|
|
||||||
|
global["eventBus"] = createEventBus();
|
||||||
|
|
||||||
|
// ==================== Forge Event Listeners ====================
|
||||||
|
|
||||||
|
ForgeEvents.onEvent(
|
||||||
|
"net.minecraftforge.event.entity.living.LivingEntityUseItemEvent$Finish",
|
||||||
|
(event) => {
|
||||||
|
eventBus.emit("LivingEntityUseItemEvent$Finish", event);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
ForgeEvents.onEvent(
|
||||||
|
"net.minecraftforge.event.entity.player.ItemFishedEvent",
|
||||||
|
(event) => {
|
||||||
|
eventBus.emit("PlayerItemFishedEvent", event);
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
/**
|
|
||||||
* @typedef {(
|
|
||||||
* "PlayerItemFishedEvent" |
|
|
||||||
* "LivingEntityUseItemEvent$Finish"
|
|
||||||
* )} EventName
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {(
|
|
||||||
* ((event: Internal.ItemFishedEvent) => any) |
|
|
||||||
* ((event: Internal.LivingEntityUseItemEvent$Finish) => any)
|
|
||||||
* )} EventCallback
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A simple event bus for handling custom events in KubeJS environment.
|
|
||||||
* @namespace
|
|
||||||
*/
|
|
||||||
const eventBus = {
|
|
||||||
/**
|
|
||||||
* Map storing event names and their corresponding callback functions.
|
|
||||||
* @type {{[key: string]: Function}}
|
|
||||||
*/
|
|
||||||
eventMap: {},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers a callback function for a specific event.
|
|
||||||
* @overload
|
|
||||||
* @param {"PlayerItemFishedEvent"} eventName - The name of the event to listen for.
|
|
||||||
* @param {(event: Internal.ItemFishedEvent) => any} callback - The callback function.
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Registers a callback function for a specific event.
|
|
||||||
* @overload
|
|
||||||
* @param {"LivingEntityUseItemEvent$Finish"} eventName - The name of the event to listen for.
|
|
||||||
* @param {(event: Internal.LivingEntityUseItemEvent$Finish) => any} callback - The callback function.
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Registers a callback function for a specific event.
|
|
||||||
* @param {EventName} eventName - The name of the event to listen for.
|
|
||||||
* @param {EventCallback} callback - The callback function to execute when the event is emitted.
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
register: function (eventName, callback) {
|
|
||||||
this.eventMap[eventName] = callback;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers a callback function for a specific event.
|
|
||||||
* @overload
|
|
||||||
* @param {"PlayerItemFishedEvent"} eventName - The name of the event to listen for.
|
|
||||||
* @param {(event: Internal.ItemFishedEvent) => any} callback - The callback function.
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Emits an event, calling the registered callback function if it exists.
|
|
||||||
* @overload
|
|
||||||
* @param {"LivingEntityUseItemEvent$Finish"} eventName - The name of the event to emit.
|
|
||||||
* @param {Internal.LivingEntityUseItemEvent$Finish} event - The event data.
|
|
||||||
* @returns {any}
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Emits an event, calling the registered callback function if it exists.
|
|
||||||
* @param {string} eventName - The name of the event to emit.
|
|
||||||
* @param {*} event - The event data to pass to the callback function.
|
|
||||||
* @returns {*} The return value of the callback function, or undefined if no callback is registered.
|
|
||||||
*/
|
|
||||||
emit: function (eventName, event) {
|
|
||||||
const callback = this.eventMap[eventName];
|
|
||||||
if (callback) {
|
|
||||||
return callback(event);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {typeof eventBus} EventBus
|
|
||||||
*/
|
|
||||||
|
|
||||||
global["eventBus"] = eventBus;
|
|
||||||
|
|
||||||
ForgeEvents.onEvent(
|
|
||||||
"net.minecraftforge.event.entity.living.LivingEntityUseItemEvent$Finish",
|
|
||||||
(event) => {
|
|
||||||
eventBus.emit("LivingEntityUseItemEvent$Finish", event);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
ForgeEvents.onEvent(
|
|
||||||
"net.minecraftforge.event.entity.player.ItemFishedEvent",
|
|
||||||
(event) => {
|
|
||||||
eventBus.emit("PlayerItemFishedEvent", event);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
19
src/startup_scripts/jsconfig.json
Normal file
19
src/startup_scripts/jsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://www.schemastore.org/jsconfig.json",
|
||||||
|
"extends": "../../jsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": [
|
||||||
|
"../../types/probe/startup/packages",
|
||||||
|
"../../types/probe/startup/global",
|
||||||
|
"../../types/probe/shared"
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"packages/*": [
|
||||||
|
"../../types/probe/startup/packages/*",
|
||||||
|
"../../types/probe/shared/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["./**/*.js", "../../types/*.d.ts"],
|
||||||
|
"exclude": []
|
||||||
|
}
|
||||||
29
types/C4.d.ts
vendored
Normal file
29
types/C4.d.ts
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Event data for C4 use started events.
|
||||||
|
*/
|
||||||
|
interface C4UseStartedEvent {
|
||||||
|
player: Internal.Player;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event data for C4 activation events.
|
||||||
|
*/
|
||||||
|
interface C4ActivatedEvent {
|
||||||
|
level: Internal.Level;
|
||||||
|
player: Internal.Player;
|
||||||
|
explosionTime: number;
|
||||||
|
explosionPower: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event data for C4 explosion events.
|
||||||
|
*/
|
||||||
|
interface C4ExplosionEvent {
|
||||||
|
level: Internal.Level;
|
||||||
|
position: {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
z: number;
|
||||||
|
};
|
||||||
|
power: number;
|
||||||
|
}
|
||||||
93
types/DataBus.d.ts
vendored
Normal file
93
types/DataBus.d.ts
vendored
Normal file
@@ -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<T>(name: string, value: T): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import a previously exported value
|
||||||
|
* @template T
|
||||||
|
* @param {string} name - Export identifier
|
||||||
|
* @returns {T} The exported value
|
||||||
|
*/
|
||||||
|
import<T extends keyof ExportTypes>(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 {};
|
||||||
48
types/EventBus.d.ts
vendored
Normal file
48
types/EventBus.d.ts
vendored
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
* Mapping of event names to their corresponding event types.
|
||||||
|
*/
|
||||||
|
interface EventMap {
|
||||||
|
PlayerItemFishedEvent: Internal.ItemFishedEvent;
|
||||||
|
LivingEntityUseItemEvent$Finish: Internal.LivingEntityUseItemEvent$Finish;
|
||||||
|
C4Activated: C4ActivatedEvent;
|
||||||
|
C4UseStarted: C4UseStartedEvent;
|
||||||
|
C4Explosion: C4ExplosionEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Union type of all valid event names.
|
||||||
|
*/
|
||||||
|
type EventName = keyof EventMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback function type for a specific event.
|
||||||
|
*/
|
||||||
|
type EventCallback<T extends EventName> = (event: EventMap[T]) => any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple event bus for handling custom events in KubeJS environment.
|
||||||
|
*/
|
||||||
|
interface EventBus {
|
||||||
|
/**
|
||||||
|
* Map storing event names and their corresponding callback functions.
|
||||||
|
*/
|
||||||
|
eventMap: { [key: string]: Function };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a callback function for a specific event.
|
||||||
|
* @param eventName - The name of the event to listen for.
|
||||||
|
* @param callback - The callback function to execute when the event is emitted.
|
||||||
|
*/
|
||||||
|
register<T extends EventName>(
|
||||||
|
eventName: T,
|
||||||
|
callback: EventCallback<T>,
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits an event, calling the registered callback function if it exists.
|
||||||
|
* @param eventName - The name of the event to emit.
|
||||||
|
* @param event - The event data to pass to the callback function.
|
||||||
|
* @returns The return value of the callback function, or undefined if no callback is registered.
|
||||||
|
*/
|
||||||
|
emit<T extends EventName>(eventName: T, event: EventMap[T]): any;
|
||||||
|
}
|
||||||
8
types/global.d.ts
vendored
Normal file
8
types/global.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export {}; // Mark the file as a module, do not remove unless there are other import/exports!
|
||||||
|
// Override the global type
|
||||||
|
declare global {
|
||||||
|
export type ProbeJS$$ResolvedGlobal = {
|
||||||
|
eventBus: EventBus;
|
||||||
|
toExplosionC4Map: { [key: string]: boolean };
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user