diff --git a/src/accesscontrol/cli.ts b/src/accesscontrol/cli.ts index 94d8efa..1b851c3 100644 --- a/src/accesscontrol/cli.ts +++ b/src/accesscontrol/cli.ts @@ -14,7 +14,9 @@ export interface AppContext { configFilepath: string; reloadConfig: () => void; logger: CCLog; - print: (message: string) => void; + print: ( + message: string | MinecraftTextComponent | MinecraftTextComponent[], + ) => void; } function getGroupNames(config: AccessConfig) { @@ -25,14 +27,14 @@ function getGroupNames(config: AccessConfig) { const addCommand: Command = { name: "add", - description: "Add player to group", + description: "添加玩家到用户组", args: [ { name: "userGroup", - description: "Group to add player to", + description: "要添加到的用户组", required: true, }, - { name: "playerName", description: "Player to add", required: true }, + { name: "playerName", description: "要添加的玩家", required: true }, ], action: ({ args, context }) => { const [groupName, playerName] = [ @@ -49,9 +51,11 @@ const addCommand: Command = { const group = config.usersGroups.find((g) => g.groupName === groupName); if (!group) { const groupNames = getGroupNames(config); - context.print( - `Invalid group: ${groupName}. Available groups: ${groupNames.join(", ")}`, - ); + context.print({ + text: `无效的用户组: ${groupName}. 可用用户组: ${groupNames.join( + ", ", + )}`, + }); return Ok.EMPTY; } group.groupUsers ??= []; @@ -62,21 +66,21 @@ const addCommand: Command = { saveConfig(config, context.configFilepath); context.reloadConfig(); - context.print(`Added player ${playerName} to ${groupName}`); + context.print({ text: `已添加玩家 ${playerName} 到 ${groupName}` }); return Ok.EMPTY; }, }; const delCommand: Command = { name: "del", - description: "Delete player from group", + description: "从用户组删除玩家", args: [ { name: "userGroup", - description: "Group to delete player from", + description: "要从中删除玩家的用户组", required: true, }, - { name: "playerName", description: "Player to delete", required: true }, + { name: "playerName", description: "要删除的玩家", required: true }, ], action: ({ args, context }) => { const [groupName, playerName] = [ @@ -85,7 +89,7 @@ const delCommand: Command = { ]; if (groupName === "admin") { - context.print("Could not delete admin, please edit config file."); + context.print({ text: "无法删除管理员, 请直接编辑配置文件。" }); return Ok.EMPTY; } @@ -94,9 +98,11 @@ const delCommand: Command = { if (!group) { const groupNames = getGroupNames(config); - context.print( - `Invalid group: ${groupName}. Available groups: ${groupNames.join(", ")}`, - ); + context.print({ + text: `无效的用户组: ${groupName}. 可用用户组: ${groupNames.join( + ", ", + )}`, + }); return Ok.EMPTY; } @@ -106,43 +112,45 @@ const delCommand: Command = { saveConfig(config, context.configFilepath); context.reloadConfig(); - context.print(`Deleted player ${playerName} from ${groupName}`); + context.print({ text: `已从 ${groupName} 中删除玩家 ${playerName}` }); return Ok.EMPTY; }, }; const listCommand: Command = { name: "list", - description: "List all players with their groups", + description: "列出所有玩家及其所在的用户组", action: ({ context }) => { const config = loadConfig(context.configFilepath)!; - let message = `Admins : [ ${config.adminGroupConfig.groupUsers.join(", ")} ]\n`; + let message = `管理员 : [ ${config.adminGroupConfig.groupUsers.join( + ", ", + )} ]\n`; for (const groupConfig of config.usersGroups) { const users = groupConfig.groupUsers ?? []; message += `${groupConfig.groupName} : [ ${users.join(", ")} ]\n`; } - context.print(message.trim()); + context.print({ text: message.trim() }); return Ok.EMPTY; }, }; const setCommand: Command = { name: "set", - description: "Config access control settings", + description: "配置访问控制设置", args: [ { name: "option", - description: "Option to set (warnInterval, detectInterval, detectRange)", + description: "要设置的选项 (warnInterval, detectInterval, detectRange)", required: true, }, - { name: "value", description: "Value to set", required: true }, + { name: "value", description: "要设置的值", required: true }, ], action: ({ args, context }) => { const [option, valueStr] = [args.option as string, args.value as string]; const value = parseInt(valueStr); if (isNaN(value)) { - context.print(`Invalid value: ${valueStr}. Must be a number.`); + context.print({ text: `无效的值: ${valueStr}. 必须是一个数字。` }); return Ok.EMPTY; } @@ -152,45 +160,45 @@ const setCommand: Command = { switch (option) { case "warnInterval": config.watchInterval = value; - message = `Set warn interval to ${value}`; + message = `已设置警告间隔为 ${value}`; break; case "detectInterval": config.detectInterval = value; - message = `Set detect interval to ${value}`; + message = `已设置检测间隔为 ${value}`; break; case "detectRange": config.detectRange = value; - message = `Set detect range to ${value}`; + message = `已设置检测范围为 ${value}`; break; default: - context.print( - `Unknown option: ${option}. Available: warnInterval, detectInterval, detectRange`, - ); + context.print({ + text: `未知选项: ${option}. 可用选项: warnInterval, detectInterval, detectRange`, + }); return Ok.EMPTY; } saveConfig(config, context.configFilepath); context.reloadConfig(); - context.print(message); + context.print({ text: message }); return Ok.EMPTY; }, }; const editGroupCommand: Command = { name: "group", - description: "Edit group properties", + description: "编辑用户组属性", args: [ { name: "groupName", - description: "Name of the group to edit", + description: "要编辑的用户组名称", required: true, }, { name: "property", - description: "Property to change (isAllowed, isNotice)", + description: "要更改的属性 (isAllowed, isNotice)", required: true, }, - { name: "value", description: "New value (true/false)", required: true }, + { name: "value", description: "新值 (true/false)", required: true }, ], action: ({ args, context }) => { const [groupName, property, valueStr] = [ @@ -208,15 +216,15 @@ const editGroupCommand: Command = { } if (!groupConfig) { - context.print(`Group ${groupName} not found`); + context.print({ text: `用户组 ${groupName} 未找到` }); return Ok.EMPTY; } const boolValue = parseBoolean(valueStr); if (boolValue === undefined) { - context.print( - `Invalid boolean value: ${valueStr}. Use 'true' or 'false'.`, - ); + context.print({ + text: `无效的布尔值: ${valueStr}. 请使用 'true' 或 'false'.`, + }); return Ok.EMPTY; } @@ -224,63 +232,65 @@ const editGroupCommand: Command = { switch (property) { case "isAllowed": groupConfig.isAllowed = boolValue; - message = `Set ${groupName}.isAllowed to ${boolValue}`; + message = `已设置 ${groupName}.isAllowed 为 ${boolValue}`; break; case "isNotice": groupConfig.isNotice = boolValue; - message = `Set ${groupName}.isNotice to ${boolValue}`; + message = `已设置 ${groupName}.isNotice 为 ${boolValue}`; break; default: - context.print( - `Unknown property: ${property}. Available: isAllowed, isNotice`, - ); + context.print({ + text: `未知属性: ${property}. 可用属性: isAllowed, isNotice`, + }); return Ok.EMPTY; } saveConfig(config, context.configFilepath); context.reloadConfig(); - context.print(message); + context.print({ text: message }); return Ok.EMPTY; }, }; const editCommand: Command = { name: "edit", - description: "Edit various configurations", + description: "编辑各项配置", subcommands: new Map([["group", editGroupCommand]]), }; const showConfigCommand: Command = { name: "showconfig", - description: "Show configuration", + description: "显示配置", options: new Map([ [ "type", { name: "type", - description: "Type of config to show (groups, toast, all)", + description: "要显示的配置类型 (groups, toast, all)", required: false, defaultValue: "all", }, ], ]), - action: ({ args, context }) => { - const type = args.type as string; + action: ({ options, context }) => { + const type = options.type as string; const config = loadConfig(context.configFilepath)!; let message = ""; switch (type) { case "groups": { - let groupsMessage = `Admin Group: ${config.adminGroupConfig.groupName}\n`; - groupsMessage += ` Users: [${config.adminGroupConfig.groupUsers.join(", ")}]\n`; - groupsMessage += ` Allowed: ${config.adminGroupConfig.isAllowed}\n`; - groupsMessage += ` notice: ${config.adminGroupConfig.isNotice}\n\n`; + let groupsMessage = `管理员组: ${config.adminGroupConfig.groupName}\n`; + groupsMessage += ` 用户: [${config.adminGroupConfig.groupUsers.join( + ", ", + )}]\n`; + groupsMessage += ` 允许: ${config.adminGroupConfig.isAllowed}\n`; + groupsMessage += ` 通知: ${config.adminGroupConfig.isNotice}\n\n`; for (const group of config.usersGroups) { - groupsMessage += `Group: ${group.groupName}\n`; - groupsMessage += ` Users: [${(group.groupUsers ?? []).join(", ")}]\n`; - groupsMessage += ` Allowed: ${group.isAllowed}\n`; - groupsMessage += ` Notice: ${group.isNotice}\n`; + groupsMessage += `用户组: ${group.groupName}\n`; + groupsMessage += ` 用户: [${(group.groupUsers ?? []).join(", ")}]\n`; + groupsMessage += ` 允许: ${group.isAllowed}\n`; + groupsMessage += ` 通知: ${group.isNotice}\n`; groupsMessage += "\n"; } message = groupsMessage.trim(); @@ -288,38 +298,48 @@ const showConfigCommand: Command = { } case "toast": { - let toastMessage = "Default Toast Config:\n"; - toastMessage += ` Title: ${config.welcomeToastConfig.title.text}\n`; - toastMessage += ` Message: ${config.welcomeToastConfig.msg.text}\n`; - toastMessage += ` Prefix: ${config.welcomeToastConfig.prefix ?? "none"}\n`; - toastMessage += ` Brackets: ${config.welcomeToastConfig.brackets ?? "none"}\n`; - toastMessage += ` Bracket Color: ${config.welcomeToastConfig.bracketColor ?? "none"}\n\n`; + let toastMessage = "默认 Toast 配置:\n"; + toastMessage += ` 标题: ${config.welcomeToastConfig.title.text}\n`; + toastMessage += ` 消息: ${config.welcomeToastConfig.msg.text}\n`; + toastMessage += ` 前缀: ${ + config.welcomeToastConfig.prefix ?? "none" + }\n`; + toastMessage += ` 括号: ${ + config.welcomeToastConfig.brackets ?? "none" + }\n`; + toastMessage += ` 括号颜色: ${ + config.welcomeToastConfig.bracketColor ?? "none" + }\n\n`; - toastMessage += "Warn Toast Config:\n"; - toastMessage += ` Title: ${config.warnToastConfig.title.text}\n`; - toastMessage += ` Message: ${config.warnToastConfig.msg.text}\n`; - toastMessage += ` Prefix: ${config.warnToastConfig.prefix ?? "none"}\n`; - toastMessage += ` Brackets: ${config.warnToastConfig.brackets ?? "none"}\n`; - toastMessage += ` Bracket Color: ${config.warnToastConfig.bracketColor ?? "none"}`; + toastMessage += "警告 Toast 配置:\n"; + toastMessage += ` 标题: ${config.warnToastConfig.title.text}\n`; + toastMessage += ` 消息: ${config.warnToastConfig.msg.text}\n`; + toastMessage += ` 前缀: ${config.warnToastConfig.prefix ?? "none"}\n`; + toastMessage += ` 括号: ${ + config.warnToastConfig.brackets ?? "none" + }\n`; + toastMessage += ` 括号颜色: ${ + config.warnToastConfig.bracketColor ?? "none" + }`; message = toastMessage; break; } case "all": { - let allMessage = `Detect Range: ${config.detectRange}\n`; - allMessage += `Detect Interval: ${config.detectInterval}\n`; - allMessage += `Warn Interval: ${config.watchInterval}\n\n`; + let allMessage = `检测范围: ${config.detectRange}\n`; + allMessage += `检测间隔: ${config.detectInterval}\n`; + allMessage += `警告间隔: ${config.watchInterval}\n\n`; allMessage += - "Use 'showconfig groups' or 'showconfig toast' for detailed view"; + "使用 'showconfig --type groups' 或 'showconfig --type toast' 查看详细信息"; message = allMessage; break; } default: - message = `Invalid type: ${type}. Available: groups, toast, all`; + message = `无效类型: ${type}. 可用类型: groups, toast, all`; break; } - context.print(message); + context.print({ text: message }); return Ok.EMPTY; }, }; @@ -327,7 +347,7 @@ const showConfigCommand: Command = { // Root command const rootCommand: Command = { name: "@AC", - description: "Access Control command line interface", + description: "访问控制命令行界面", subcommands: new Map([ ["add", addCommand], ["del", delCommand], @@ -337,7 +357,25 @@ const rootCommand: Command = { ["showconfig", showConfigCommand], ]), action: ({ context }) => { - context.print("Welcome to Access Control CLI"); + context.print([ + { + text: "请使用 ", + }, + { + text: "@AC --help", + clickEvent: { + action: "copy_to_clipboard", + value: "@AC --help", + }, + hoverEvent: { + action: "show_text", + value: "点击复制命令", + }, + }, + { + text: " 获取门禁系统更详细的命令说明😊😊😊", + }, + ]); return Ok.EMPTY; }, }; diff --git a/src/accesscontrol/config.ts b/src/accesscontrol/config.ts index 01debe7..5d2e03e 100644 --- a/src/accesscontrol/config.ts +++ b/src/accesscontrol/config.ts @@ -12,6 +12,7 @@ interface UserGroupConfig { groupName: string; isAllowed: boolean; isNotice: boolean; + isWelcome: boolean; groupUsers: string[]; } @@ -39,6 +40,7 @@ const defaultConfig: AccessConfig = { groupUsers: ["Selcon"], isAllowed: true, isNotice: true, + isWelcome: true, }, usersGroups: [ { @@ -46,57 +48,60 @@ const defaultConfig: AccessConfig = { groupUsers: [], isAllowed: true, isNotice: true, + isWelcome: false, }, { groupName: "VIP", groupUsers: [], isAllowed: true, isNotice: false, + isWelcome: true, }, { groupName: "enemies", groupUsers: [], isAllowed: false, isNotice: false, + isWelcome: false, }, ], welcomeToastConfig: { title: { - text: "Welcome", + text: "欢迎", color: "green", }, msg: { - text: "Hello User %playerName%", - color: "green", + text: "欢迎 %playerName% 参观桃源星喵~", + color: "#EDC8DA", }, - prefix: "Taohuayuan", - brackets: "[]", + prefix: "桃源星", + brackets: "<>", bracketColor: "", }, noticeToastConfig: { title: { - text: "Notice", + text: "警告", color: "red", }, msg: { - text: "Unfamiliar player %playerName% appeared at Position %playerPosX%, %playerPosY%, %playerPosZ%", + text: "陌生玩家 %playerName% 出现在 %playerPosX%, %playerPosY%, %playerPosZ%", color: "red", }, - prefix: "Taohuayuan", - brackets: "[]", + prefix: "桃源星", + brackets: "<>", bracketColor: "", }, warnToastConfig: { title: { - text: "Attention!!!", + text: "注意", color: "red", }, msg: { - text: "%playerName% you are not allowed to be here", + text: "%playerName% 你已经进入桃源星领地", color: "red", }, - prefix: "Taohuayuan", - brackets: "[]", + prefix: "桃源星", + brackets: "<>", bracketColor: "", }, }; diff --git a/src/accesscontrol/main.ts b/src/accesscontrol/main.ts index 5e87fc7..636c878 100644 --- a/src/accesscontrol/main.ts +++ b/src/accesscontrol/main.ts @@ -7,6 +7,7 @@ import { deepCopy } from "@/lib/common"; import { ReadWriteLock } from "@/lib/mutex/ReadWriteLock"; import { ChatManager } from "@/lib/ChatManager"; import { gTimerManager } from "@/lib/TimerManager"; +import { KeyEvent, pullEventAs } from "@/lib/event"; const args = [...$vararg]; @@ -25,17 +26,20 @@ logger.info("Load config successfully!"); logger.debug(textutils.serialise(config, { allow_repetitions: true })); // Peripheral -const playerDetector = peripheralManager.findByNameRequired("playerDetector"); -const chatBox = peripheralManager.findByNameRequired("chatBox"); +const playerDetector = peripheral.find( + "playerDetector", +)[0] as PlayerDetectorPeripheral; +const chatBox = peripheral.find("chatBox")[0] as ChatBoxPeripheral; const chatManager: ChatManager = new ChatManager([chatBox]); // Global -let inRangePlayers: string[] = []; -let watchPlayersInfo: { name: string; hasNoticeTimes: number }[] = []; +let gInRangePlayers: string[] = []; +let gWatchPlayersInfo: { name: string; hasNoticeTimes: number }[] = []; +let gIsRunning = true; interface ParseParams { - name?: string; - group?: string; + playerName?: string; + groupName?: string; info?: PlayerInfo; } @@ -47,8 +51,8 @@ function reloadConfig() { } config = loadConfig(configFilepath)!; - inRangePlayers = []; - watchPlayersInfo = []; + gInRangePlayers = []; + gWatchPlayersInfo = []; releaser.release(); logger.info("Reload config successfully!"); } @@ -56,7 +60,7 @@ function reloadConfig() { function safeParseTextComponent( component: MinecraftTextComponent, params?: ParseParams, -): string { +): MinecraftTextComponent { const newComponent = deepCopy(component); if (newComponent.text == undefined) { @@ -64,11 +68,11 @@ function safeParseTextComponent( } else if (newComponent.text.includes("%")) { newComponent.text = newComponent.text.replace( "%playerName%", - params?.name ?? "UnknowPlayer", + params?.playerName ?? "UnknowPlayer", ); newComponent.text = newComponent.text.replace( "%groupName%", - params?.group ?? "UnknowGroup", + params?.groupName ?? "UnknowGroup", ); newComponent.text = newComponent.text.replace( "%playerPosX%", @@ -83,7 +87,34 @@ function safeParseTextComponent( params?.info?.z.toString() ?? "UnknowPosZ", ); } - return textutils.serialiseJSON(newComponent); + return newComponent; +} + +function sendMessage( + toastConfig: ToastConfig, + targetPlayer: string, + params: ParseParams, +) { + let releaser = configLock.tryAcquireRead(); + while (releaser === undefined) { + sleep(0.1); + releaser = configLock.tryAcquireRead(); + } + + chatManager.sendMessage({ + message: safeParseTextComponent( + toastConfig.msg ?? config.welcomeToastConfig.msg, + params, + ), + prefix: toastConfig.prefix ?? config.welcomeToastConfig.prefix, + brackets: toastConfig.brackets ?? config.welcomeToastConfig.brackets, + bracketColor: + toastConfig.bracketColor ?? config.welcomeToastConfig.bracketColor, + targetPlayer: targetPlayer, + utf8Support: true, + }); + + releaser.release(); } function sendToast( @@ -134,7 +165,7 @@ function sendNotice(player: string, playerInfo?: PlayerInfo) { for (const targetPlayer of noticeTargetPlayers) { if (!onlinePlayers.includes(targetPlayer)) continue; sendToast(config.noticeToastConfig, targetPlayer, { - name: player, + playerName: player, info: playerInfo, }); sleep(1); @@ -152,10 +183,10 @@ function sendWarn(player: string) { releaser = configLock.tryAcquireRead(); } - sendToast(config.warnToastConfig, player, { name: player }); + sendToast(config.warnToastConfig, player, { playerName: player }); chatManager.sendMessage({ message: safeParseTextComponent(config.warnToastConfig.msg, { - name: player, + playerName: player, }), targetPlayer: player, prefix: "AccessControl", @@ -166,18 +197,18 @@ function sendWarn(player: string) { } function watchLoop() { - while (true) { + while (gIsRunning) { const releaser = configLock.tryAcquireRead(); if (releaser === undefined) { os.sleep(1); continue; } - const watchPlayerNames = watchPlayersInfo.flatMap((value) => value.name); + const watchPlayerNames = gWatchPlayersInfo.flatMap((value) => value.name); logger.debug(`Watch Players [ ${watchPlayerNames.join(", ")} ]`); - for (const player of watchPlayersInfo) { + for (const player of gWatchPlayersInfo) { const playerInfo = playerDetector.getPlayerPos(player.name); - if (inRangePlayers.includes(player.name)) { + if (gInRangePlayers.includes(player.name)) { // Notice if (player.hasNoticeTimes < config.noticeTimes) { sendNotice(player.name, playerInfo); @@ -193,7 +224,7 @@ function watchLoop() { ); } else { // Get rid of player from list - watchPlayersInfo = watchPlayersInfo.filter( + gWatchPlayersInfo = gWatchPlayersInfo.filter( (value) => value.name != player.name, ); logger.info( @@ -209,7 +240,7 @@ function watchLoop() { } function mainLoop() { - while (true) { + while (gIsRunning) { const releaser = configLock.tryAcquireRead(); if (releaser === undefined) { os.sleep(0.1); @@ -221,20 +252,31 @@ function mainLoop() { logger.debug(`Detected ${players.length} players: ${playersList}`); for (const player of players) { - if (inRangePlayers.includes(player)) continue; + if (gInRangePlayers.includes(player)) continue; + + // Get player Info + const playerInfo = playerDetector.getPlayerPos(player); if (config.adminGroupConfig.groupUsers.includes(player)) { - logger.info(`Admin ${player} appear`); + logger.info( + `Admin ${player} appear at ${playerInfo?.x}, ${playerInfo?.y}, ${playerInfo?.z}`, + ); + if (config.adminGroupConfig.isWelcome) + sendMessage(config.welcomeToastConfig, player, { + playerName: player, + groupName: "Admin", + info: playerInfo, + }); continue; } // New player appear - const playerInfo = playerDetector.getPlayerPos(player); let groupConfig: UserGroupConfig = { groupName: "Unfamiliar", groupUsers: [], isAllowed: false, isNotice: false, + isWelcome: false, }; for (const userGroupConfig of config.usersGroups) { if (userGroupConfig.groupUsers == undefined) continue; @@ -247,28 +289,37 @@ function mainLoop() { break; } + + if (config.adminGroupConfig.isWelcome) + sendMessage(config.welcomeToastConfig, player, { + playerName: player, + groupName: groupConfig.groupName, + info: playerInfo, + }); if (groupConfig.isAllowed) continue; logger.warn( `${groupConfig.groupName} ${player} appear at ${playerInfo?.x}, ${playerInfo?.y}, ${playerInfo?.z}`, ); if (config.isWarn) sendWarn(player); - watchPlayersInfo = [ - ...watchPlayersInfo, + gWatchPlayersInfo = [ + ...gWatchPlayersInfo, { name: player, hasNoticeTimes: 0 }, ]; } - inRangePlayers = players; + gInRangePlayers = players; releaser.release(); os.sleep(config.detectInterval); } } function keyboardLoop() { - while (true) { - const [eventType, key] = os.pullEvent("key"); - if (eventType === "key" && key === keys.c) { + while (gIsRunning) { + const event = pullEventAs(KeyEvent, "key"); + if (event === undefined) continue; + + if (event.key === keys.c) { logger.info("Launching Access Control TUI..."); try { logger.setInTerminal(false); @@ -280,7 +331,12 @@ function keyboardLoop() { logger.setInTerminal(true); reloadConfig(); } + } else if (event.key === keys.r) { + reloadConfig(); } + // else if (event.key === keys.q) { + // gIsRunning = false; + // } } } @@ -300,7 +356,7 @@ function cliLoop() { }), }); - while (true) { + while (gIsRunning) { const result = chatManager.getReceivedMessage(); if (result.isErr()) { sleep(0.5); @@ -342,9 +398,11 @@ function main(args: string[]) { logger.info("Starting access control system, get args: " + args.join(", ")); if (args.length == 1) { if (args[0] == "start") { - print( - "Access Control System started. Press 'c' to open configuration TUI.", - ); + const tutorial: string[] = []; + tutorial.push("Access Control System started."); + tutorial.push("\tPress 'c' to open configuration TUI."); + tutorial.push("\tPress 'r' to reload configuration."); + print(tutorial.join("\n")); parallel.waitForAll( () => mainLoop(), () => gTimerManager.run(), diff --git a/src/accesscontrol/tui.ts b/src/accesscontrol/tui.ts index 6868053..309028c 100644 --- a/src/accesscontrol/tui.ts +++ b/src/accesscontrol/tui.ts @@ -355,6 +355,34 @@ const AccessControlTUI = () => { { class: "flex flex-col ml-2" }, label({}, () => `Group: ${getSelectedGroup().groupName}`), + div( + { class: "flex flex-row" }, + label({}, "Is Welcome:"), + input({ + type: "checkbox", + checked: () => getSelectedGroup().isWelcome, + onChange: (checked) => { + const groupIndex = selectedGroupIndex(); + if (groupIndex === 0) { + const currentAdmin = config().adminGroupConfig; + setConfig("adminGroupConfig", { + ...currentAdmin, + isWelcome: checked, + }); + } else { + const actualIndex = groupIndex - 1; + const currentGroups = config().usersGroups; + const currentGroup = currentGroups[actualIndex]; + const newGroups = [...currentGroups]; + newGroups[actualIndex] = { + ...currentGroup, + isWelcome: checked, + }; + setConfig("usersGroups", newGroups); + } + }, + }), + ), div( { class: "flex flex-row" }, label({}, "Is Allowed:"), @@ -541,7 +569,12 @@ const AccessControlTUI = () => { label({}, "Prefix:"), input({ type: "text", - value: () => getTempToastConfig().prefix, + value: () => { + const str = textutils.serialiseJSON(getTempToastConfig().prefix, { + unicode_strings: true, + }); + return str.substring(1, str.length - 1); + }, onInput: (value) => setTempToastConfig({ ...getTempToastConfig(), prefix: value }), onFocusChanged: () => { diff --git a/src/lib/ChatManager.ts b/src/lib/ChatManager.ts index 18e3cfa..6f5fc11 100644 --- a/src/lib/ChatManager.ts +++ b/src/lib/ChatManager.ts @@ -53,7 +53,7 @@ export interface ChatToast extends ChatBasicMessage { /** Target player username to send the toast to */ targetPlayer: string; /** Title of the toast notification */ - title: string; + title: string | MinecraftTextComponent | MinecraftTextComponent[]; } /** @@ -224,9 +224,13 @@ export class ChatManager { // Send private message to specific player if (typeof message.message === "string") { [success, errorMsg] = chatbox.sendMessageToPlayer( - message.message, + textutils.serialiseJSON(message.message, { + unicode_strings: message.utf8Support, + }), message.targetPlayer, - message.prefix, + textutils.serialiseJSON(message.prefix ?? "AP", { + unicode_strings: message.utf8Support, + }), message.brackets, message.bracketColor, message.range, @@ -236,11 +240,13 @@ export class ChatManager { // Handle MinecraftTextComponent for private message [success, errorMsg] = chatbox.sendFormattedMessageToPlayer( textutils.serialiseJSON(message.message, { - unicode_strings: true, + unicode_strings: message.utf8Support, allow_repetitions: true, }), message.targetPlayer, - message.prefix, + textutils.serialiseJSON(message.prefix ?? "AP", { + unicode_strings: message.utf8Support, + }), message.brackets, message.bracketColor, message.range, @@ -251,8 +257,12 @@ export class ChatManager { // Send global message if (typeof message.message === "string") { [success, errorMsg] = chatbox.sendMessage( - message.message, - message.prefix, + textutils.serialiseJSON(message.message, { + unicode_strings: message.utf8Support, + }), + textutils.serialiseJSON(message.prefix ?? "AP", { + unicode_strings: message.utf8Support, + }), message.brackets, message.bracketColor, message.range, @@ -262,10 +272,12 @@ export class ChatManager { // Handle MinecraftTextComponent for global message [success, errorMsg] = chatbox.sendFormattedMessage( textutils.serialiseJSON(message.message, { - unicode_strings: true, + unicode_strings: message.utf8Support, allow_repetitions: true, }), - message.prefix, + textutils.serialiseJSON(message.prefix ?? "AP", { + unicode_strings: message.utf8Support, + }), message.brackets, message.bracketColor, message.range, @@ -321,10 +333,16 @@ export class ChatManager { typeof toast.title === "string" ) { [success, errorMsg] = chatbox.sendToastToPlayer( - toast.message, - toast.title, + textutils.serialiseJSON(toast.message, { + unicode_strings: toast.utf8Support, + }), + textutils.serialiseJSON(toast.title, { + unicode_strings: toast.utf8Support, + }), toast.targetPlayer, - toast.prefix, + textutils.serialiseJSON(toast.prefix ?? "AP", { + unicode_strings: toast.utf8Support, + }), toast.brackets, toast.bracketColor, toast.range, @@ -337,21 +355,23 @@ export class ChatManager { ? toast.message : textutils.serialiseJSON(toast.message, { unicode_strings: true, - allow_repetitions: true, + allow_repetitions: toast.utf8Support, }); const titleJson = typeof toast.title === "string" ? toast.title : textutils.serialiseJSON(toast.title, { unicode_strings: true, - allow_repetitions: true, + allow_repetitions: toast.utf8Support, }); [success, errorMsg] = chatbox.sendFormattedToastToPlayer( messageJson, titleJson, toast.targetPlayer, - toast.prefix, + textutils.serialiseJSON(toast.prefix ?? "AP", { + unicode_strings: toast.utf8Support, + }), toast.brackets, toast.bracketColor, toast.range, diff --git a/types/advanced-peripherals/shared.d.ts b/types/advanced-peripherals/shared.d.ts index c94bd2b..b4167dd 100644 --- a/types/advanced-peripherals/shared.d.ts +++ b/types/advanced-peripherals/shared.d.ts @@ -30,7 +30,7 @@ declare type MinecraftColor = | "light_purple" | "yellow" | "white" - | "reset"; // RGB color in #RRGGBB format + | `#${string}`; declare type MinecraftFont = | "minecraft:default" diff --git a/types/craftos/index.d.ts b/types/craftos/index.d.ts index 95fb026..6155c62 100644 --- a/types/craftos/index.d.ts +++ b/types/craftos/index.d.ts @@ -925,10 +925,22 @@ declare namespace textutils { function pagedTabulate(...args: (LuaTable | Object | Color)[]): void; function serialize(tab: object, options?: SerializeOptions): string; function serialise(tab: object, options?: SerializeOptions): string; - function serializeJSON(tab: object, nbtStyle?: boolean): string; - function serializeJSON(tab: object, options: SerializeJSONOptions): string; - function serialiseJSON(tab: object, nbtStyle?: boolean): string; - function serialiseJSON(tab: object, options: SerializeJSONOptions): string; + function serializeJSON( + tab: object | string | number | boolean, + nbtStyle?: boolean, + ): string; + function serializeJSON( + tab: object | string | number | boolean, + options: SerializeJSONOptions, + ): string; + function serialiseJSON( + tab: object | string | number | boolean, + nbtStyle?: boolean, + ): string; + function serialiseJSON( + tab: object | string | number | boolean, + options: SerializeJSONOptions, + ): string; function unserialize(str: string): unknown; function unserialise(str: string): unknown; function unserializeJSON(