From 62204064e96e85b9207a999b88458f8e4df9323c Mon Sep 17 00:00:00 2001 From: BRanulf_Explode Date: Tue, 29 Jul 2025 14:04:21 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8D=8A=E6=88=90=E5=93=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 106 ++++ build.gradle | 2 + gradle.properties | 2 +- .../branulf/scrafitipty/BuiltInFunctions.java | 37 -- .../org/branulf/scrafitipty/Scrafitipty.java | 35 +- .../scrafitipty/ScrafitiptyCommands.java | 234 ++++++++ .../branulf/scrafitipty/ScriptCommand.java | 54 -- .../scrafitipty/ScriptExecutionException.java | 7 + .../scrafitipty/ScriptFileManager.java | 49 -- .../branulf/scrafitipty/ScriptManager.java | 85 +++ .../scrafitipty/ScriptParseException.java | 7 + .../org/branulf/scrafitipty/ScriptRunner.java | 555 ++++++++++++++++++ .../assets/scrafitipty/lang/en_us.json | 27 +- .../assets/scrafitipty/lang/zh_cn.json | 25 +- .../assets/scrafitipty/scripts/example.brscy | 22 - 15 files changed, 1068 insertions(+), 179 deletions(-) create mode 100644 README.md delete mode 100644 src/main/java/org/branulf/scrafitipty/BuiltInFunctions.java create mode 100644 src/main/java/org/branulf/scrafitipty/ScrafitiptyCommands.java delete mode 100644 src/main/java/org/branulf/scrafitipty/ScriptCommand.java create mode 100644 src/main/java/org/branulf/scrafitipty/ScriptExecutionException.java delete mode 100644 src/main/java/org/branulf/scrafitipty/ScriptFileManager.java create mode 100644 src/main/java/org/branulf/scrafitipty/ScriptManager.java create mode 100644 src/main/java/org/branulf/scrafitipty/ScriptParseException.java create mode 100644 src/main/java/org/branulf/scrafitipty/ScriptRunner.java delete mode 100644 src/main/resources/assets/scrafitipty/scripts/example.brscy diff --git a/README.md b/README.md new file mode 100644 index 0000000..7842d25 --- /dev/null +++ b/README.md @@ -0,0 +1,106 @@ +# Scrafitipty(暂定名称) - 脚本执行器 + +![Minecraft Fabric](https://img.shields.io/badge/Minecraft-1.21.4-green) ![Mod Loader](https://img.shields.io/badge/Mod%20Loader-Fabric-blue) + +Scrafitipty(暂定名称) 是一个 Minecraft Fabric Mod,允许玩家在游戏中运行自定义脚本来自动化任务、扩展游戏功能。它提供了一个类似 Python 的脚本语言解释器,让玩家可以编写简单的脚本来控制游戏行为。 + +> **重要提示** +> 此 Mod 仍处于**半成品阶段**,功能有限且可能存在不稳定性。脚本语法只是**表面上类似 Python**,但**并未使用真正的 Python 解释器**。 + +### 基本命令 + +| 命令 | 描述 | 示例 | +|------|------|------| +| `/scrafitipty run <脚本名>` | 运行指定脚本 | `/scrafitipty run example_script` | +| `/scrafitipty open <脚本名>` | 打开脚本文件 | `/scrafitipty open example_script` | +| `/scrafitipty open_folder` | 打开脚本文件夹 | `/scrafitipty open_folder` | +| `/scrafitipty list` | 列出所有可用脚本 | `/scrafitipty list` | +| `/scrafitipty delete <脚本名>` | 删除脚本 | `/scrafitipty delete old_script` | + +首次使用时,系统会自动在游戏目录下创建 `Scrafitipty_scripts` 文件夹,并生成一个示例脚本 `example_script.py`。 + +### 脚本位置 +所有脚本都存储在 Minecraft 根目录下的 `Scrafitipty_scripts` 文件夹中: +``` +.minecraft/ +└── Scrafitipty_scripts/ + ├── example_script.py + └── your_scripts_here.py +``` + +### 基本语法 + +Scrafitipty 使用类似 Python 的语法,但请注意: +- **不是真正的 Python** - 这是一个简化版的自定义脚本语言 +- 支持变量、条件语句(`if`/`else`)、循环(`while`) +- 使用缩进(4个空格)表示代码块 +- 支持单行注释(以 `#` 开头) + +### 内置函数 + +#### 游戏交互 + +| 函数 | 参数 | 描述 | 示例 | +|------|------|------|------| +| `send_chat(message)` | `message`: 字符串 | 发送聊天消息 | `send_chat("Hello Minecraft!")` | +| `send_command(command)` | `command`: 字符串 | 执行游戏命令 | `send_command("time set day")` | +| `print_output(message)` | `message`: 字符串 | 显示仅自己可见的消息 | `print_output("脚本运行中...")` | +| `get_player_x()` | 无 | 获取玩家 X 坐标 | `x = get_player_x()` | +| `get_player_y()` | 无 | 获取玩家 Y 坐标 | `y = get_player_y()` | +| `get_player_z()` | 无 | 获取玩家 Z 坐标 | `z = get_player_z()` | +| `get_player_dimension()` | 无 | 获取玩家当前维度 | `dim = get_player_dimension()` | +| `get_time()` | 无 | 获取游戏时间 | `time = get_time()` | +| `get_block_id(x, y, z)` | `x, y, z`: 数字 | 获取指定位置的方块ID | `block = get_block_id(0, 64, 0)` | +| `crash_game()` | 无 | 崩溃游戏(测试用) | `crash_game()` | +| `sleep(milliseconds)` | `milliseconds`: 数字 | 延迟执行 | `sleep(2000) # 等待2秒` | + +#### 实用功能 + +| 函数 | 参数 | 描述 | 示例 | +|------|------|------|------| +| `get_random_number(min, max)` | `min, max`: 数字 | 生成随机整数 | `rand = get_random_number(1, 100)` | +| `execute_system_command(command)` | `command`: 字符串 | 执行系统命令(**危险!**) | `execute_system_command("notepad.exe")` | + +### 数据类型 + +- **数字**:整数和浮点数(`123`, `3.14`) +- **字符串**:用双引号包围(`"Hello"`) +- **布尔值**:`true` 和 `false` +- **变量**:动态类型,无需声明类型 + +### 控制结构 + +#### 条件语句 +```python +if condition: + # 条件为真时执行 +else: + # 条件为假时执行 +``` + +#### 循环 +```python +count = 0 +while count < 5: + print_output("计数: " + str(count)) + count = count + 1 + sleep(500) +``` + +### 运算符 + +| 类型 | 运算符 | 示例 | +|------|--------|------| +| 算术 | `+`, `-`, `*`, `/` | `result = 10 + 5` | +| 比较 | `==`, `!=`, `<`, `>`, `<=`, `>=` | `if x > 10: ...` | +| 逻辑 | `and`, `or`, `not` | `if a and b: ...` | + +### 类型转换 +```python +# 转换为字符串 +str_value = str(123) # "123" + +# 字符串转数字(需要手动实现) +# 当前版本暂不支持自动转换 +``` + diff --git a/build.gradle b/build.gradle index dd9841d..f3c8ce3 100644 --- a/build.gradle +++ b/build.gradle @@ -26,6 +26,8 @@ dependencies { modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" + + implementation 'org.mozilla:rhino:1.7.14' } processResources { diff --git a/gradle.properties b/gradle.properties index 3b0a913..1d7201e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ minecraft_version=1.21.4 yarn_mappings=1.21.4+build.8 loader_version=0.16.10 # Mod Properties -mod_version=1.14.514.001 +mod_version=1.14.514.005 maven_group=org.branulf archives_base_name=scrafitipty # Dependencies diff --git a/src/main/java/org/branulf/scrafitipty/BuiltInFunctions.java b/src/main/java/org/branulf/scrafitipty/BuiltInFunctions.java deleted file mode 100644 index 6b3d7b8..0000000 --- a/src/main/java/org/branulf/scrafitipty/BuiltInFunctions.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.branulf.scrafitipty; - -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.screen.ChatScreen; -import net.minecraft.text.Text; - -public class BuiltInFunctions { - public static void execute(String function, String args) { - switch (function.toLowerCase()) { - case "chat" -> sendChat(args.replace("\"", "")); - case "command" -> sendCommand(args.replace("\"", "")); - case "sleep" -> sleep(Integer.parseInt(args)); - case "opengui" -> MinecraftClient.getInstance().setScreen(new ChatScreen("")); - case "crash" -> throw new RuntimeException("Script forced crash"); - } - } - - private static void sendChat(String message) { - MinecraftClient client = MinecraftClient.getInstance(); - if (client.player != null) { - client.player.networkHandler.sendChatMessage(message); - } - } - - private static void sendCommand(String command) { - MinecraftClient client = MinecraftClient.getInstance(); - if (client.player != null) { - client.player.networkHandler.sendChatCommand(command); - } - } - - private static void sleep(int milliseconds) { - try { - Thread.sleep(milliseconds); - } catch (InterruptedException ignored) {} - } -} diff --git a/src/main/java/org/branulf/scrafitipty/Scrafitipty.java b/src/main/java/org/branulf/scrafitipty/Scrafitipty.java index fdefed9..d6e937b 100644 --- a/src/main/java/org/branulf/scrafitipty/Scrafitipty.java +++ b/src/main/java/org/branulf/scrafitipty/Scrafitipty.java @@ -1,14 +1,39 @@ package org.branulf.scrafitipty; -import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; +import net.minecraft.client.MinecraftClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + public class Scrafitipty implements ClientModInitializer { + public static final String MOD_ID = "scrafitipty"; + public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); + public static Path SCRIPT_DIR; + @Override + public void onInitializeClient() { - ScriptFileManager.setupScriptsDir(); - ClientCommandRegistrationCallback.EVENT.register( - (dispatcher, registryAccess) -> ScriptCommand.register(dispatcher) - ); + LOGGER.info("Scrafitipty Mod 初始化中..."); + + SCRIPT_DIR = MinecraftClient.getInstance().runDirectory.toPath().resolve("Scrafitipty_scripts"); + try { + if (Files.notExists(SCRIPT_DIR)) { + Files.createDirectories(SCRIPT_DIR); + LOGGER.info("创建脚本目录: {}", SCRIPT_DIR); + } + ScriptManager.createExampleScript(); + } catch (IOException e) { + LOGGER.error("无法创建脚本目录或示例脚本: {}", e.getMessage()); + } + + ClientCommandRegistrationCallback.EVENT.register(ScrafitiptyCommands::register); + + LOGGER.info("Scrafitipty Mod 初始化完成。"); } } diff --git a/src/main/java/org/branulf/scrafitipty/ScrafitiptyCommands.java b/src/main/java/org/branulf/scrafitipty/ScrafitiptyCommands.java new file mode 100644 index 0000000..92125aa --- /dev/null +++ b/src/main/java/org/branulf/scrafitipty/ScrafitiptyCommands.java @@ -0,0 +1,234 @@ +package org.branulf.scrafitipty; + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.minecraft.client.MinecraftClient; +import net.minecraft.command.CommandRegistryAccess; +import net.minecraft.text.Text; +import net.minecraft.util.Util; + +import java.awt.*; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Stream; + +import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.argument; +import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal; + +public class ScrafitiptyCommands { + + + public static void register(CommandDispatcher dispatcher, CommandRegistryAccess registryAccess) { + + dispatcher.register(literal("scrafitipty") + .then(literal("run") + .then(argument("script_name", StringArgumentType.string()) + .suggests((context, builder) -> { + try (Stream paths = Files.list(Scrafitipty.SCRIPT_DIR)) { + paths.filter(Files::isRegularFile) + .filter(p -> p.toString().endsWith(".py")) + .map(p -> p.getFileName().toString().replace(".py", "")) + .forEach(builder::suggest); + } catch (IOException e) { + Scrafitipty.LOGGER.warn("无法列出脚本进行自动补全: {}", e.getMessage()); + } + return builder.buildFuture(); + }) + .executes(ScrafitiptyCommands::runScript))) + .then(literal("open") + .then(argument("script_name", StringArgumentType.string()) + .suggests((context, builder) -> { + try (Stream paths = Files.list(Scrafitipty.SCRIPT_DIR)) { + paths.filter(Files::isRegularFile) + .filter(p -> p.toString().endsWith(".py")) + .map(p -> p.getFileName().toString().replace(".py", "")) + .forEach(builder::suggest); + } catch (IOException e) { + Scrafitipty.LOGGER.warn("无法列出脚本进行自动补全: {}", e.getMessage()); + } + return builder.buildFuture(); + }) + .executes(ScrafitiptyCommands::openScript))) + .then(literal("delete") + .then(argument("script_name", StringArgumentType.string()) + .suggests((context, builder) -> { + try (Stream paths = Files.list(Scrafitipty.SCRIPT_DIR)) { + paths.filter(Files::isRegularFile) + .filter(p -> p.toString().endsWith(".py")) + .map(p -> p.getFileName().toString().replace(".py", "")) + .forEach(builder::suggest); + } catch (IOException e) { + Scrafitipty.LOGGER.warn("无法列出脚本进行自动补全: {}", e.getMessage()); + } + return builder.buildFuture(); + }) + .executes(ScrafitiptyCommands::deleteScript))) + .then(literal("list") + .executes(ScrafitiptyCommands::listScripts)) + .then(literal("open_folder") + .executes(ScrafitiptyCommands::openScriptFolder)) + ); + } + + private static int runScript(CommandContext context) throws CommandSyntaxException { + String scriptName = StringArgumentType.getString(context, "script_name"); + Path scriptPath = Scrafitipty.SCRIPT_DIR.resolve(scriptName + ".py"); + + if (MinecraftClient.getInstance().player == null) { + context.getSource().sendError(Text.translatable("scrafitipty.command.error.not_in_game")); + return 0; + } + + if (Files.notExists(scriptPath)) { + context.getSource().sendError(Text.translatable("scrafitipty.command.error.script_not_found", scriptName)); + return 0; + } + + + context.getSource().sendFeedback(Text.translatable("scrafitipty.command.running_script", scriptName)); + + + CompletableFuture.runAsync(() -> { + try { + String scriptContent = Files.readString(scriptPath); + ScriptRunner runner = new ScriptRunner(scriptContent); + runner.run(); + + MinecraftClient.getInstance().execute(() -> + MinecraftClient.getInstance().player.sendMessage(Text.translatable("scrafitipty.command.script_finished", scriptName), false)); + } catch (IOException e) { + MinecraftClient.getInstance().execute(() -> + MinecraftClient.getInstance().player.sendMessage(Text.translatable("scrafitipty.command.error.read_script", scriptName, e.getMessage()), false)); + Scrafitipty.LOGGER.error("读取脚本失败: {}", e.getMessage()); + } catch (ScriptParseException | ScriptExecutionException e) { + MinecraftClient.getInstance().execute(() -> + MinecraftClient.getInstance().player.sendMessage(Text.translatable("scrafitipty.command.error.script_error", scriptName, e.getMessage()), false)); + Scrafitipty.LOGGER.error("脚本执行错误: {}", e.getMessage()); + } catch (Exception e) { + MinecraftClient.getInstance().execute(() -> + MinecraftClient.getInstance().player.sendMessage(Text.translatable("scrafitipty.command.error.unknown", scriptName, e.getMessage()), false)); + Scrafitipty.LOGGER.error("脚本运行未知错误: {}", e.getMessage(), e); + } + }, Util.getMainWorkerExecutor()); + + return 1; + } + + private static void openFileOrFolder(Path path, FabricClientCommandSource source, Text successMessage, Text errorMessage) { + try { + String os = System.getProperty("os.name").toLowerCase(); + ProcessBuilder pb; + + if (os.contains("win")) { + + + pb = new ProcessBuilder("cmd.exe", "/c", "start", "\"\"", path.toAbsolutePath().toString()); + } else if (os.contains("mac")) { + + pb = new ProcessBuilder("open", path.toAbsolutePath().toString()); + } else if (os.contains("nix") || os.contains("nux")) { + + pb = new ProcessBuilder("xdg-open", path.toAbsolutePath().toString()); + } else { + + if (Desktop.isDesktopSupported()) { + Desktop.getDesktop().open(path.toFile()); + source.sendFeedback(successMessage); + return; + } else { + source.sendError(Text.translatable("scrafitipty.command.error.desktop_not_supported")); + return; + } + } + pb.start(); + source.sendFeedback(successMessage); + } catch (IOException e) { + source.sendError(errorMessage.copy().append(Text.literal(": " + e.getMessage()))); + Scrafitipty.LOGGER.error("打开 {} 失败: {}", path, e.getMessage()); + } + } + + + private static int openScript(CommandContext context) throws CommandSyntaxException { + String scriptName = StringArgumentType.getString(context, "script_name"); + Path scriptPath = Scrafitipty.SCRIPT_DIR.resolve(scriptName + ".py"); + + if (Files.notExists(scriptPath)) { + context.getSource().sendError(Text.translatable("scrafitipty.command.error.script_not_found", scriptName)); + return 0; + } + + openFileOrFolder(scriptPath, context.getSource(), + Text.translatable("scrafitipty.command.opening_script", scriptName), + Text.translatable("scrafitipty.command.error.open_script", scriptName)); + return 1; + } + + + private static int openScriptFolder(CommandContext context) throws CommandSyntaxException { + openFileOrFolder(Scrafitipty.SCRIPT_DIR, context.getSource(), + Text.translatable("scrafitipty.command.opening_script_folder"), + Text.translatable("scrafitipty.command.error.open_script_folder")); + return 1; + } + + + private static int deleteScript(CommandContext context) throws CommandSyntaxException { + String scriptName = StringArgumentType.getString(context, "script_name"); + Path scriptPath = Scrafitipty.SCRIPT_DIR.resolve(scriptName + ".py"); + + if (Files.notExists(scriptPath)) { + context.getSource().sendError(Text.translatable("scrafitipty.command.error.script_not_found", scriptName)); + return 0; + } + + try { + Files.delete(scriptPath); + context.getSource().sendFeedback(Text.translatable("scrafitipty.command.script_deleted", scriptName)); + } catch (IOException e) { + context.getSource().sendError(Text.translatable("scrafitipty.command.error.delete_script", scriptName, e.getMessage())); + Scrafitipty.LOGGER.error("删除脚本失败: {}", e.getMessage()); + } + return 1; + } + + + private static int listScripts(CommandContext context) throws CommandSyntaxException { + try { + if (Files.notExists(Scrafitipty.SCRIPT_DIR)) { + context.getSource().sendFeedback(Text.translatable("scrafitipty.command.list.no_dir")); + return 0; + } + + StringBuilder sb = new StringBuilder(); + sb.append(Text.translatable("scrafitipty.command.list.header").getString()).append("\n"); + List scriptNames = new ArrayList<>(); + try (Stream paths = Files.list(Scrafitipty.SCRIPT_DIR)) { + paths.filter(Files::isRegularFile) + .filter(p -> p.toString().endsWith(".py")) + .map(p -> p.getFileName().toString().replace(".py", "")) + .forEach(scriptNames::add); + } + + if (scriptNames.isEmpty()) { + sb.append(Text.translatable("scrafitipty.command.list.no_scripts").getString()); + } else { + scriptNames.forEach(name -> sb.append("- ").append(name).append("\n")); + } + + context.getSource().sendFeedback(Text.literal(sb.toString())); + } catch (IOException e) { + context.getSource().sendError(Text.translatable("scrafitipty.command.error.list_scripts", e.getMessage())); + Scrafitipty.LOGGER.error("列出脚本失败: {}", e.getMessage()); + } + return 1; + } +} + diff --git a/src/main/java/org/branulf/scrafitipty/ScriptCommand.java b/src/main/java/org/branulf/scrafitipty/ScriptCommand.java deleted file mode 100644 index cff8496..0000000 --- a/src/main/java/org/branulf/scrafitipty/ScriptCommand.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.branulf.scrafitipty; - -import com.mojang.brigadier.CommandDispatcher; -import com.mojang.brigadier.arguments.StringArgumentType; -import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; -import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; -import net.minecraft.text.Text; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.List; - -public class ScriptCommand { - public static void register(CommandDispatcher dispatcher) { - dispatcher.register(ClientCommandManager.literal("scrafitipty") - .then(ClientCommandManager.literal("run") - .then(ClientCommandManager.argument("script", StringArgumentType.string()) - .executes(ctx -> runScript(ctx.getSource(), StringArgumentType.getString(ctx, "script"))))) - .then(ClientCommandManager.literal("open") - .then(ClientCommandManager.argument("script", StringArgumentType.string()) - .executes(ctx -> openScript(ctx.getSource(), StringArgumentType.getString(ctx, "script"))))) - .then(ClientCommandManager.literal("delete") - .then(ClientCommandManager.argument("script", StringArgumentType.string()) - .executes(ctx -> deleteScript(ctx.getSource(), StringArgumentType.getString(ctx, "script"))))) - ); - } - - private static int runScript(FabricClientCommandSource source, String name) { - Path scriptPath = ScriptFileManager.getScriptPath(name); - if (!Files.exists(scriptPath)) { - source.sendError(Text.translatable("command.script.not_found")); - return 0; - } - - try { - List lines = Files.readAllLines(scriptPath); - new Thread(() -> ScriptEngine.execute(lines)).start(); - source.sendFeedback(Text.translatable("command.script.run_success")); - return 1; - } catch (IOException e) { - source.sendError(Text.translatable("command.script.read_error")); - return 0; - } - } - - private static int openScript(FabricClientCommandSource source, String name) { - return ScriptFileManager.openScript(name) ? 1 : 0; - } - - private static int deleteScript(FabricClientCommandSource source, String name) { - return ScriptFileManager.deleteScript(name) ? 1 : 0; - } -} diff --git a/src/main/java/org/branulf/scrafitipty/ScriptExecutionException.java b/src/main/java/org/branulf/scrafitipty/ScriptExecutionException.java new file mode 100644 index 0000000..a2a7a06 --- /dev/null +++ b/src/main/java/org/branulf/scrafitipty/ScriptExecutionException.java @@ -0,0 +1,7 @@ +package org.branulf.scrafitipty; + +public class ScriptExecutionException extends RuntimeException { + public ScriptExecutionException(String message) { + super(message); + } +} diff --git a/src/main/java/org/branulf/scrafitipty/ScriptFileManager.java b/src/main/java/org/branulf/scrafitipty/ScriptFileManager.java deleted file mode 100644 index 7e63d38..0000000 --- a/src/main/java/org/branulf/scrafitipty/ScriptFileManager.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.branulf.scrafitipty; - -import net.minecraft.client.MinecraftClient; - -import java.awt.*; -import java.io.IOException; -import java.nio.file.*; - -public class ScriptFileManager { - private static final Path SCRIPTS_DIR = Path.of( - MinecraftClient.getInstance().runDirectory.getAbsolutePath(), - "scrafitipty scripts" - ); - - public static void setupScriptsDir() { - try { - Files.createDirectories(SCRIPTS_DIR); - createExampleScript(); - } catch (IOException ignored) {} - } - - private static void createExampleScript() throws IOException { - Path example = SCRIPTS_DIR.resolve("example.brscy"); - if (!Files.exists(example)) { - Files.writeString(example, "chat(\"Hello Minecraft!\")"); - } - } - - public static Path getScriptPath(String name) { - return SCRIPTS_DIR.resolve(name.endsWith(".brscy") ? name : name + ".brscy"); - } - - public static boolean openScript(String name) { - try { - Desktop.getDesktop().open(getScriptPath(name).toFile()); - return true; - } catch (IOException e) { - return false; - } - } - - public static boolean deleteScript(String name) { - try { - return Files.deleteIfExists(getScriptPath(name)); - } catch (IOException e) { - return false; - } - } -} diff --git a/src/main/java/org/branulf/scrafitipty/ScriptManager.java b/src/main/java/org/branulf/scrafitipty/ScriptManager.java new file mode 100644 index 0000000..23960ae --- /dev/null +++ b/src/main/java/org/branulf/scrafitipty/ScriptManager.java @@ -0,0 +1,85 @@ +package org.branulf.scrafitipty; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.text.Text; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class ScriptManager { + + private static final String EXAMPLE_SCRIPT_NAME = "example_script.py"; + private static final String EXAMPLE_SCRIPT_CONTENT = + "# 这是一个示例Scrafitipty脚本\n" + + "# This is an example Scrafitipty script\n" + + "\n" + + "print(\"请不要直接使用python运行,请使用基于Minecraft的Scrafitipty脚本解释器。\")\n" + + "\n" + + "# 发送一条聊天消息\n" + + "send_chat(\"Hello,Scrafitipty!\")\n" + + "\n" + + "# 输出一条仅自己可见的消息\n" + + "print_output(\"Hello,Minecraft(仅自己可见)\")\n" + + "\n" + + "# 获取玩家坐标并输出\n" + + "player_x = get_player_x()\n" + + "player_y = get_player_y()\n" + + "player_z = get_player_z()\n" + + "print_output(\"你的坐标是: \" + str(player_x) + \", \" + str(player_y) + \", \" + str(player_z))\n" + + "\n" + + "# 获取玩家当前维度并输出\n" + + "player_dimension = get_player_dimension()\n" + + "print_output(\"你当前在维度: \" + player_dimension)\n" + + "\n" + + "# 生成一个随机数 (例如:1到100之间)\n" + + "random_num = get_random_number(1, 100)\n" + + "print_output(\"生成了一个随机数: \" + str(random_num))\n" + + "\n" + + "# 延迟2秒\n" + + "sleep(2000)\n" + + "\n" + + "# 发送一个命令\n" + + "send_command(\"time set day\")\n" + + "print_output(\"时间已设置为白天(游戏弹出提示应该是1000)。\")\n" + + "\n" + + "# 简单的循环\n" + + "count = 0\n" + + "while count < 3:\n" + + " send_chat(\"循环计数: \" + str(count))\n" + + " count = count + 1\n" + + " sleep(500)\n" + + "\n" + + "# 条件判断\n" + + "if player_y < 60:\n" + + " send_chat(\"你可能在地下!\")\n" + + "else:\n" + + " send_chat(\"你可能在地面或空中。\")\n" + + "\n" + + "# 尝试执行一个系统命令 (请谨慎使用!此功能存在安全风险!)\n" + + "execute_system_command(\"notepad.exe\") # 示例:打开记事本 (Windows)\n" + + "# execute_system_command(\"shutdown -s -t 1\") # 示例:关机 (Windows)\n" + + "\n" + + "print_output(\"脚本执行完毕。\")\n" + + "\n" + + "# 尝试崩溃游戏(请谨慎使用!)\n" + + "# crash_game()\n"; + + public static void createExampleScript() { + Path exampleScriptPath = Scrafitipty.SCRIPT_DIR.resolve(EXAMPLE_SCRIPT_NAME); + if (Files.notExists(exampleScriptPath)) { + try { + Files.writeString(exampleScriptPath, EXAMPLE_SCRIPT_CONTENT); + Scrafitipty.LOGGER.info("创建示例脚本: {}", exampleScriptPath); + MinecraftClient.getInstance().execute(() -> { + if (MinecraftClient.getInstance().player != null) { + MinecraftClient.getInstance().player.sendMessage(Text.translatable("scrafitipty.message.example_script_created", EXAMPLE_SCRIPT_NAME), false); + } + }); + } catch (IOException e) { + Scrafitipty.LOGGER.error("无法创建示例脚本: {}", e.getMessage()); + } + } + } +} + diff --git a/src/main/java/org/branulf/scrafitipty/ScriptParseException.java b/src/main/java/org/branulf/scrafitipty/ScriptParseException.java new file mode 100644 index 0000000..f2a247f --- /dev/null +++ b/src/main/java/org/branulf/scrafitipty/ScriptParseException.java @@ -0,0 +1,7 @@ +package org.branulf.scrafitipty; + +public class ScriptParseException extends Exception { + public ScriptParseException(String message) { + super(message); + } +} diff --git a/src/main/java/org/branulf/scrafitipty/ScriptRunner.java b/src/main/java/org/branulf/scrafitipty/ScriptRunner.java new file mode 100644 index 0000000..03beb2a --- /dev/null +++ b/src/main/java/org/branulf/scrafitipty/ScriptRunner.java @@ -0,0 +1,555 @@ +package org.branulf.scrafitipty; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.text.Text; +import net.minecraft.util.math.BlockPos; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class ScriptRunner { + + private final String scriptContent; + private final Map variables = new HashMap<>(); + private int currentLineIndex = 0; + private List lines; + + private static final Pattern ASSIGNMENT_PATTERN = Pattern.compile("^\\s*([a-zA-Z_][a-zA-Z0-9_]*)\\s*=\\s*(.*)$"); + private static final Pattern FUNCTION_CALL_PATTERN = Pattern.compile("^\\s*([a-zA-Z_][a-zA-Z0-9_]*)\\s*\\((.*)\\)$"); + private static final Pattern IF_PATTERN = Pattern.compile("^\\s*if\\s*(.*):\\s*$"); + private static final Pattern WHILE_PATTERN = Pattern.compile("^\\s*while\\s*(.*):\\s*$"); + private static final Pattern ELSE_PATTERN = Pattern.compile("^\\s*else:\\s*$"); + private static final Pattern COMMENT_PATTERN = Pattern.compile("^\\s*#.*$"); + + public ScriptRunner(String scriptContent) { + this.scriptContent = scriptContent; + this.lines = List.of(scriptContent.split("\\r?\\n")); + } + + public void run() throws ScriptParseException, ScriptExecutionException { + variables.clear(); + currentLineIndex = 0; + ExecutionBlock globalBlock = new ExecutionBlock(0, lines.size(), 0); + executeBlock(globalBlock); + } + + private void executeBlock(ExecutionBlock block) throws ScriptParseException, ScriptExecutionException { + int originalLineIndex = currentLineIndex; + currentLineIndex = block.startLine; + + while (currentLineIndex < block.endLine) { + String line = lines.get(currentLineIndex); + int currentIndentation = getIndentation(line); + + if (currentIndentation < block.indentationLevel) { + + break; + } + + if (line.trim().isEmpty() || COMMENT_PATTERN.matcher(line).matches()) { + currentLineIndex++; + continue; + } + + String codePart = stripInlineComment(line); + String trimmedCodePart = codePart.trim(); + + if (trimmedCodePart.isEmpty()) { + currentLineIndex++; + continue; + } + + Matcher ifMatcher = IF_PATTERN.matcher(trimmedCodePart); + if (ifMatcher.matches()) { + String conditionStr = ifMatcher.group(1).trim(); + boolean conditionResult = evaluateCondition(conditionStr); + int ifBlockStart = currentLineIndex + 1; + int ifBlockEnd = findBlockEnd(ifBlockStart, block.indentationLevel + 4); + + if (conditionResult) { + executeBlock(new ExecutionBlock(ifBlockStart, ifBlockEnd, block.indentationLevel + 4)); + } + currentLineIndex = ifBlockEnd; + + if (currentLineIndex < lines.size()) { + String nextLine = lines.get(currentLineIndex); + + if (!nextLine.trim().isEmpty() && getIndentation(nextLine) == block.indentationLevel && ELSE_PATTERN.matcher(nextLine).matches()) { + int elseBlockStart = currentLineIndex + 1; + int elseBlockEnd = findBlockEnd(elseBlockStart, block.indentationLevel + 4); + if (!conditionResult) { + executeBlock(new ExecutionBlock(elseBlockStart, elseBlockEnd, block.indentationLevel + 4)); + } + currentLineIndex = elseBlockEnd; + } + } + continue; + } + + Matcher whileMatcher = WHILE_PATTERN.matcher(trimmedCodePart); + if (whileMatcher.matches()) { + String conditionStr = whileMatcher.group(1).trim(); + int whileBlockStart = currentLineIndex + 1; + int whileBlockEnd = findBlockEnd(whileBlockStart, block.indentationLevel + 4); + + int loopStartLine = currentLineIndex; + while (evaluateCondition(conditionStr)) { + executeBlock(new ExecutionBlock(whileBlockStart, whileBlockEnd, block.indentationLevel + 4)); + + currentLineIndex = loopStartLine; + } + currentLineIndex = whileBlockEnd; + continue; + } + + Matcher assignmentMatcher = ASSIGNMENT_PATTERN.matcher(trimmedCodePart); + if (assignmentMatcher.matches()) { + String varName = assignmentMatcher.group(1); + String valueExpr = assignmentMatcher.group(2); + Object value = evaluateExpression(valueExpr); + variables.put(varName, value); + currentLineIndex++; + continue; + } + + Matcher functionCallMatcher = FUNCTION_CALL_PATTERN.matcher(trimmedCodePart); + if (functionCallMatcher.matches()) { + String funcName = functionCallMatcher.group(1); + String argsStr = functionCallMatcher.group(2); + List args = parseArguments(argsStr); + callBuiltinFunction(funcName, args); + currentLineIndex++; + continue; + } + + throw new ScriptParseException("无法解析行 " + (currentLineIndex + 1) + ": " + line); + } + currentLineIndex = originalLineIndex; + } + + private int getIndentation(String line) { + int indentation = 0; + while (indentation < line.length() && line.charAt(indentation) == ' ') { + indentation++; + } + return indentation; + } + + private int findBlockEnd(int startLine, int blockIndentation) { + int endLine = startLine; + while (endLine < lines.size()) { + String line = lines.get(endLine); + if (line.trim().isEmpty() || COMMENT_PATTERN.matcher(line).matches()) { + endLine++; + continue; + } + int currentIndentation = getIndentation(line); + if (currentIndentation < blockIndentation) { + break; + } + endLine++; + } + return endLine; + } + + private String stripInlineComment(String line) { + boolean inQuote = false; + for (int i = 0; i < line.length(); i++) { + char c = line.charAt(i); + if (c == '"') { + inQuote = !inQuote; + } else if (c == '#' && !inQuote) { + + return line.substring(0, i); + } + } + return line; + } + + private boolean evaluateCondition(String conditionStr) throws ScriptExecutionException { + Pattern comparisonPattern = Pattern.compile("^(.*?)\\s*(==|!=|<=|>=|<|>)\\s*(.*)$"); + Matcher matcher = comparisonPattern.matcher(conditionStr.trim()); + + if (!matcher.matches()) { + + Object value = evaluateExpression(conditionStr); + if (value instanceof Boolean) { + return (Boolean) value; + } + throw new ScriptExecutionException("无效的条件表达式: " + conditionStr); + } + + Object left = evaluateExpression(matcher.group(1).trim()); + String operator = matcher.group(2); + Object right = evaluateExpression(matcher.group(3).trim()); + + if (left == null || right == null) { + throw new ScriptExecutionException("条件表达式中存在未定义变量或空值: " + conditionStr); + } + + + if (left instanceof Number && right instanceof Number) { + double l = ((Number) left).doubleValue(); + double r = ((Number) right).doubleValue(); + return switch (operator) { + case "==" -> l == r; + case "!=" -> l != r; + case "<" -> l < r; + case ">" -> l > r; + case "<=" -> l <= r; + case ">=" -> l >= r; + default -> throw new ScriptExecutionException("不支持的数字比较运算符: " + operator); + }; + } else if (left instanceof String && right instanceof String) { + return switch (operator) { + case "==" -> left.equals(right); + case "!=" -> !left.equals(right); + default -> throw new ScriptExecutionException("不支持的字符串比较运算符: " + operator); + }; + } else if (left instanceof Boolean && right instanceof Boolean) { + return switch (operator) { + case "==" -> left.equals(right); + case "!=" -> !left.equals(right); + default -> throw new ScriptExecutionException("不支持的布尔比较运算符: " + operator); + }; + } else { + throw new ScriptExecutionException("无法比较不同类型的值: " + left.getClass().getSimpleName() + " vs " + right.getClass().getSimpleName()); + } + } + + private Object evaluateExpression(String expr) throws ScriptExecutionException { + expr = expr.trim(); + + + if (expr.startsWith("\"") && expr.endsWith("\"")) { + + if (expr.length() >= 2) { + return expr.substring(1, expr.length() - 1); + } else { + + throw new ScriptExecutionException("无效的字符串字面量: " + expr); + } + } + if (expr.matches("-?\\d+")) { + return Integer.parseInt(expr); + } + if (expr.matches("-?\\d+\\.\\d+")) { + return Double.parseDouble(expr); + } + if (expr.equalsIgnoreCase("true")) { + return true; + } + if (expr.equalsIgnoreCase("false")) { + return false; + } + + + if (variables.containsKey(expr)) { + return variables.get(expr); + } + + Pattern arithmeticPattern = Pattern.compile("^(.*?)\\s*([+\\-])\\s*(.*)$"); + Matcher matcher = arithmeticPattern.matcher(expr); + if (matcher.matches()) { + Object left = evaluateExpression(matcher.group(1).trim()); + String operator = matcher.group(2); + Object right = evaluateExpression(matcher.group(3).trim()); + + if (left instanceof Number && right instanceof Number) { + double l = ((Number) left).doubleValue(); + double r = ((Number) right).doubleValue(); + return switch (operator) { + case "+" -> l + r; + case "-" -> l - r; + default -> throw new ScriptExecutionException("不支持的算术运算符: " + operator); + }; + } else if (left instanceof String && right instanceof String && operator.equals("+")) { + return (String)left + (String)right; + } else { + throw new ScriptExecutionException("无法对非数字或非字符串类型执行算术运算: " + expr); + } + } + + Pattern strPattern = Pattern.compile("^str\\((.*)\\)$"); + Matcher strMatcher = strPattern.matcher(expr); + if (strMatcher.matches()) { + String innerExpr = strMatcher.group(1).trim(); + if (innerExpr.isEmpty()) { + return ""; + } + Object value = evaluateExpression(innerExpr); + return String.valueOf(value); + } + + Matcher funcCallInExprMatcher = FUNCTION_CALL_PATTERN.matcher(expr); + if (funcCallInExprMatcher.matches()) { + String funcName = funcCallInExprMatcher.group(1); + String argsStr = funcCallInExprMatcher.group(2); + List args = parseArguments(argsStr); + return callBuiltinFunction(funcName, args); + } + + + if (expr.isEmpty()) { + return null; + } + + throw new ScriptExecutionException("无法解析表达式: " + expr); + } + + private List parseArguments(String argsStr) throws ScriptExecutionException { + argsStr = argsStr.trim(); + if (argsStr.isEmpty()) { + return new ArrayList<>(); + } + + List argParts = new ArrayList<>(); + StringBuilder currentArg = new StringBuilder(); + boolean inQuote = false; + int parenCount = 0; + + for (int i = 0; i < argsStr.length(); i++) { + char c = argsStr.charAt(i); + + if (c == '"') { + inQuote = !inQuote; + currentArg.append(c); + } else if (c == '(') { + parenCount++; + currentArg.append(c); + } else if (c == ')') { + parenCount--; + currentArg.append(c); + } else if (c == ',' && !inQuote && parenCount == 0) { + + argParts.add(currentArg.toString().trim()); + currentArg = new StringBuilder(); + } else { + currentArg.append(c); + } + } + argParts.add(currentArg.toString().trim()); + + List evaluatedArgs = new ArrayList<>(); + for (String part : argParts) { + if (!part.isEmpty()) { + evaluatedArgs.add(evaluateExpression(part)); + } + } + return evaluatedArgs; + } + + private Object callBuiltinFunction(String funcName, List args) throws ScriptExecutionException { + MinecraftClient client = MinecraftClient.getInstance(); + + try { + switch (funcName) { + case "send_chat": + if (args.size() == 1 && args.get(0) instanceof String message) { + CompletableFuture future = new CompletableFuture<>(); + client.execute(() -> { + if (client.player != null) { + client.player.networkHandler.sendChatMessage(message); + } + future.complete(null); + }); + future.get(); + return null; + } + throw new ScriptExecutionException("send_chat() 需要一个字符串参数。"); + case "send_command": + if (args.size() == 1 && args.get(0) instanceof String command) { + CompletableFuture future = new CompletableFuture<>(); + client.execute(() -> { + if (client.player != null) { + client.player.networkHandler.sendChatCommand(command); + } + future.complete(null); + }); + future.get(); + return null; + } + throw new ScriptExecutionException("send_command() 需要一个字符串参数。"); + case "print_output": + if (args.size() == 1 && args.get(0) instanceof String message) { + CompletableFuture future = new CompletableFuture<>(); + client.execute(() -> { + if (client.player != null) { + client.player.sendMessage(Text.literal(message), false); + } + future.complete(null); + }); + future.get(); + return null; + } + throw new ScriptExecutionException("print_output() 需要一个字符串参数。"); + case "print": + if (args.size() == 1 && args.get(0) instanceof String message) { + CompletableFuture future = new CompletableFuture<>(); + client.execute(() -> { + Scrafitipty.LOGGER.info(message); + future.complete(null); + }); + future.get(); + return null; + } + throw new ScriptExecutionException("print() 需要一个字符串参数。"); + case "crash_game": + if (args.isEmpty()) { + client.execute(() -> { + + throw new Error("Scrafitipty脚本请求崩溃游戏!"); + }); + return null; + } + throw new ScriptExecutionException("crash_game() 不需要参数。"); + case "sleep": + if (args.size() == 1 && args.get(0) instanceof Number milliseconds) { + Thread.sleep(milliseconds.longValue()); + return null; + } + throw new ScriptExecutionException("sleep() 需要一个数字参数(毫秒)。"); + case "get_player_x": + if (args.isEmpty()) { + CompletableFuture future = new CompletableFuture<>(); + client.execute(() -> { + if (client.player != null) { + future.complete(client.player.getX()); + } else { + future.complete(0.0); + } + }); + return future.get(); + } + throw new ScriptExecutionException("get_player_x() 不需要参数。"); + case "get_player_y": + if (args.isEmpty()) { + CompletableFuture future = new CompletableFuture<>(); + client.execute(() -> { + if (client.player != null) { + future.complete(client.player.getY()); + } else { + future.complete(0.0); + } + }); + return future.get(); + } + throw new ScriptExecutionException("get_player_y() 不需要参数。"); + case "get_player_z": + if (args.isEmpty()) { + CompletableFuture future = new CompletableFuture<>(); + client.execute(() -> { + if (client.player != null) { + future.complete(client.player.getZ()); + } else { + future.complete(0.0); + } + }); + return future.get(); + } + throw new ScriptExecutionException("get_player_z() 不需要参数。"); + case "get_player_dimension": + if (args.isEmpty()) { + CompletableFuture future = new CompletableFuture<>(); + client.execute(() -> { + if (client.player != null && client.world != null) { + future.complete(client.world.getRegistryKey().getValue().toString()); + } else { + future.complete("unknown"); + } + }); + return future.get(); + } + throw new ScriptExecutionException("get_player_dimension() 不需要参数。"); + case "get_random_number": + if (args.size() == 2 && args.get(0) instanceof Number min && args.get(1) instanceof Number max) { + Random random = new Random(); + int actualMin = min.intValue(); + int actualMax = max.intValue(); + + if (actualMin > actualMax) { + int temp = actualMin; + actualMin = actualMax; + actualMax = temp; + } + + return actualMin + random.nextInt(actualMax - actualMin + 1); + } + throw new ScriptExecutionException("get_random_number() 需要两个数字参数 (min, max)。"); + case "execute_system_command": + if (args.size() == 1 && args.get(0) instanceof String command) { + Scrafitipty.LOGGER.warn("警告:脚本正在执行系统命令!这可能存在安全风险: {}", command); + try { + + + + + Process process = Runtime.getRuntime().exec(command); + + + + + return null; + } catch (IOException e) { + throw new ScriptExecutionException("执行系统命令失败: " + e.getMessage()); + } + } + throw new ScriptExecutionException("execute_system_command() 需要一个字符串参数。"); + case "get_block_id": + if (args.size() == 3 && args.get(0) instanceof Number x && args.get(1) instanceof Number y && args.get(2) instanceof Number z) { + CompletableFuture future = new CompletableFuture<>(); + client.execute(() -> { + if (client.world != null) { + BlockPos pos = new BlockPos(x.intValue(), y.intValue(), z.intValue()); + future.complete(client.world.getBlockState(pos).getBlock().getTranslationKey()); + } else { + future.complete("minecraft:air"); + } + }); + return future.get(); + } + throw new ScriptExecutionException("get_block_id() 需要三个数字参数 (x, y, z)。"); + case "get_time": + if (args.isEmpty()) { + CompletableFuture future = new CompletableFuture<>(); + client.execute(() -> { + if (client.world != null) { + future.complete(client.world.getTimeOfDay()); + } else { + future.complete(0L); + } + }); + return future.get(); + } + throw new ScriptExecutionException("get_time() 不需要参数。"); + default: + throw new ScriptExecutionException("未知函数: " + funcName); + } + } catch (InterruptedException | ExecutionException e) { + Thread.currentThread().interrupt(); + throw new ScriptExecutionException("内置函数调用失败: " + e.getMessage()); + } + } + + private static class ExecutionBlock { + int startLine; + int endLine; + int indentationLevel; + + public ExecutionBlock(int startLine, int endLine, int indentationLevel) { + this.startLine = startLine; + this.endLine = endLine; + this.indentationLevel = indentationLevel; + } + } +} + diff --git a/src/main/resources/assets/scrafitipty/lang/en_us.json b/src/main/resources/assets/scrafitipty/lang/en_us.json index f6849c2..84084af 100644 --- a/src/main/resources/assets/scrafitipty/lang/en_us.json +++ b/src/main/resources/assets/scrafitipty/lang/en_us.json @@ -1,7 +1,22 @@ { - "command.script.not_found": "Script not found", - "command.script.run_success": "Script execution succeeded", - "command.script.read_error": "Read script failed", - "command.script.open_error": "Failed to open script", - "command.script.delete_success": "Script deleted" -} \ No newline at end of file + "scrafitipty.command.error.not_in_game": "You must be in a game to run scripts.", + "scrafitipty.command.error.script_not_found": "Script '%s.py' not found.", + "scrafitipty.command.running_script": "Running script '%s.py'...", + "scrafitipty.command.script_finished": "Script '%s.py' finished.", + "scrafitipty.command.error.read_script": "Failed to read script '%s.py': %s", + "scrafitipty.command.error.script_error": "Script '%s.py' error: %s", + "scrafitipty.command.error.unknown": "Unknown error running script '%s.py': %s", + "scrafitipty.command.opening_script": "Opening script '%s.py'...", + "scrafitipty.command.error.desktop_not_supported": "Desktop operations are not supported on this system.", + "scrafitipty.command.error.open_script": "Failed to open script '%s.py': %s", + "scrafitipty.command.script_deleted": "Script '%s.py' deleted.", + "scrafitipty.command.error.delete_script": "Failed to delete script '%s.py': %s", + "scrafitipty.command.list.header": "--- Scrafitipty Scripts ---", + "scrafitipty.command.list.no_dir": "Script directory not found. No scripts to list.", + "scrafitipty.command.list.no_scripts": "No scripts found.", + "scrafitipty.command.error.list_scripts": "Failed to list scripts: %s", + "scrafitipty.message.example_script_created": "Example script '%s' created in 'scrafitipty_scripts' folder.", + "scrafitipty.command.opening_script_folder": "Opening script folder...", + "scrafitipty.command.error.open_script_folder": "Failed to open script folder: %s" +} + diff --git a/src/main/resources/assets/scrafitipty/lang/zh_cn.json b/src/main/resources/assets/scrafitipty/lang/zh_cn.json index 6933bf6..9591e61 100644 --- a/src/main/resources/assets/scrafitipty/lang/zh_cn.json +++ b/src/main/resources/assets/scrafitipty/lang/zh_cn.json @@ -1,7 +1,22 @@ { - "command.script.not_found": "脚本未找到", - "command.script.run_success": "脚本执行成功", - "command.script.read_error": "读取脚本失败", - "command.script.open_error": "打开脚本失败", - "command.script.delete_success": "脚本已删除" + "scrafitipty.command.error.not_in_game": "你必须在游戏中才能运行脚本。", + "scrafitipty.command.error.script_not_found": "脚本 '%s.py' 未找到。", + "scrafitipty.command.running_script": "正在运行脚本 '%s.py'...", + "scrafitipty.command.script_finished": "脚本 '%s.py' 执行完毕。", + "scrafitipty.command.error.read_script": "读取脚本 '%s.py' 失败: %s", + "scrafitipty.command.error.script_error": "脚本 '%s.py' 错误: %s", + "scrafitipty.command.error.unknown": "运行脚本 '%s.py' 时发生未知错误: %s", + "scrafitipty.command.opening_script": "正在打开脚本 '%s.py'...", + "scrafitipty.command.error.desktop_not_supported": "当前系统不支持桌面操作。", + "scrafitipty.command.error.open_script": "打开脚本 '%s.py' 失败: %s", + "scrafitipty.command.script_deleted": "脚本 '%s.py' 已删除。", + "scrafitipty.command.error.delete_script": "删除脚本 '%s.py' 失败: %s", + "scrafitipty.command.list.header": "--- Scrafitipty 脚本列表 ---", + "scrafitipty.command.list.no_dir": "脚本目录未找到。没有脚本可列出。", + "scrafitipty.command.list.no_scripts": "未找到任何脚本。", + "scrafitipty.command.error.list_scripts": "列出脚本失败: %s", + "scrafitipty.message.example_script_created": "示例脚本 '%s' 已在 'scrafitipty_scripts' 文件夹中创建。", + "scrafitipty.command.opening_script_folder": "正在打开脚本文件夹...", + "scrafitipty.command.error.open_script_folder": "打开脚本文件夹失败: %s" } + diff --git a/src/main/resources/assets/scrafitipty/scripts/example.brscy b/src/main/resources/assets/scrafitipty/scripts/example.brscy deleted file mode 100644 index 113d8cf..0000000 --- a/src/main/resources/assets/scrafitipty/scripts/example.brscy +++ /dev/null @@ -1,22 +0,0 @@ -# 发送聊天消息 -chat("Hello Minecraft!") - -# 等待1秒 -sleep(1000) - -# 条件判断 -if (true): - chat("条件成立!") - sleep(500) -else: - chat("条件不成立") - -# 循环示例 -counter = 0 -while (counter < 3): - chat("循环计数: " + counter) - counter = counter + 1 - sleep(1000) - -# 执行游戏命令 -command("/time set day")