mirror of
https://github.com/SikongJueluo/cc-utils.git
synced 2025-11-29 12:57:50 +08:00
refactor(logging): migrate from CCLog to structured Logger
This commit is contained in:
@@ -1,4 +1,3 @@
|
|||||||
import { CCLog, DAY, LogLevel } from "@/lib/ccLog";
|
|
||||||
import { ToastConfig, UserGroupConfig, loadConfig } from "./config";
|
import { ToastConfig, UserGroupConfig, loadConfig } from "./config";
|
||||||
import { createAccessControlCli } from "./cli";
|
import { createAccessControlCli } from "./cli";
|
||||||
import { launchAccessControlTUI } from "./tui";
|
import { launchAccessControlTUI } from "./tui";
|
||||||
@@ -7,14 +6,31 @@ import { ReadWriteLock } from "@/lib/mutex/ReadWriteLock";
|
|||||||
import { ChatManager } from "@/lib/ChatManager";
|
import { ChatManager } from "@/lib/ChatManager";
|
||||||
import { gTimerManager } from "@/lib/TimerManager";
|
import { gTimerManager } from "@/lib/TimerManager";
|
||||||
import { KeyEvent, pullEventAs } from "@/lib/event";
|
import { KeyEvent, pullEventAs } from "@/lib/event";
|
||||||
|
import {
|
||||||
|
ConditionalStream,
|
||||||
|
ConsoleStream,
|
||||||
|
DAY,
|
||||||
|
FileStream,
|
||||||
|
Logger,
|
||||||
|
LogLevel,
|
||||||
|
processor,
|
||||||
|
textRenderer,
|
||||||
|
} from "@/lib/ccStructLog";
|
||||||
|
|
||||||
const args = [...$vararg];
|
const args = [...$vararg];
|
||||||
|
|
||||||
// Init Log
|
// Init Log
|
||||||
const logger = new CCLog("accesscontrol.log", {
|
let isOnConsoleStream = true;
|
||||||
printTerminal: true,
|
const logger = new Logger({
|
||||||
logInterval: DAY,
|
processors: [
|
||||||
outputMinLevel: LogLevel.Info,
|
processor.filterByLevel(LogLevel.Info),
|
||||||
|
processor.addTimestamp(),
|
||||||
|
],
|
||||||
|
renderer: textRenderer,
|
||||||
|
streams: [
|
||||||
|
new ConditionalStream(new ConsoleStream(), () => isOnConsoleStream),
|
||||||
|
new FileStream("accesscontrol.log", DAY),
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
// Load Config
|
// Load Config
|
||||||
@@ -26,7 +42,7 @@ logger.debug(textutils.serialise(config, { allow_repetitions: true }));
|
|||||||
|
|
||||||
// Peripheral
|
// Peripheral
|
||||||
const playerDetector = peripheral.find(
|
const playerDetector = peripheral.find(
|
||||||
"playerDetector",
|
"playerDetector",
|
||||||
)[0] as PlayerDetectorPeripheral;
|
)[0] as PlayerDetectorPeripheral;
|
||||||
const chatBox = peripheral.find("chatBox")[0] as ChatBoxPeripheral;
|
const chatBox = peripheral.find("chatBox")[0] as ChatBoxPeripheral;
|
||||||
const chatManager: ChatManager = new ChatManager([chatBox]);
|
const chatManager: ChatManager = new ChatManager([chatBox]);
|
||||||
@@ -37,410 +53,419 @@ let gWatchPlayersInfo: { name: string; hasNoticeTimes: number }[] = [];
|
|||||||
let gIsRunning = true;
|
let gIsRunning = true;
|
||||||
|
|
||||||
interface ParseParams {
|
interface ParseParams {
|
||||||
playerName?: string;
|
playerName?: string;
|
||||||
groupName?: string;
|
groupName?: string;
|
||||||
info?: PlayerInfo;
|
info?: PlayerInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
function reloadConfig() {
|
function reloadConfig() {
|
||||||
let releaser = configLock.tryAcquireWrite();
|
let releaser = configLock.tryAcquireWrite();
|
||||||
while (releaser === undefined) {
|
while (releaser === undefined) {
|
||||||
sleep(1);
|
sleep(1);
|
||||||
releaser = configLock.tryAcquireWrite();
|
releaser = configLock.tryAcquireWrite();
|
||||||
}
|
}
|
||||||
|
|
||||||
config = loadConfig(configFilepath)!;
|
config = loadConfig(configFilepath)!;
|
||||||
gInRangePlayers = [];
|
gInRangePlayers = [];
|
||||||
gWatchPlayersInfo = [];
|
gWatchPlayersInfo = [];
|
||||||
releaser.release();
|
releaser.release();
|
||||||
logger.info("Reload config successfully!");
|
logger.info("Reload config successfully!");
|
||||||
}
|
}
|
||||||
|
|
||||||
function safeParseTextComponent(
|
function safeParseTextComponent(
|
||||||
component: MinecraftTextComponent,
|
component: MinecraftTextComponent,
|
||||||
params?: ParseParams,
|
params?: ParseParams,
|
||||||
): MinecraftTextComponent {
|
): MinecraftTextComponent {
|
||||||
const newComponent = deepCopy(component);
|
const newComponent = deepCopy(component);
|
||||||
|
|
||||||
if (newComponent.text == undefined) {
|
if (newComponent.text == undefined) {
|
||||||
newComponent.text = "Wrong text, please contanct with admin";
|
newComponent.text = "Wrong text, please contanct with admin";
|
||||||
} else if (newComponent.text.includes("%")) {
|
} else if (newComponent.text.includes("%")) {
|
||||||
newComponent.text = newComponent.text.replace(
|
newComponent.text = newComponent.text.replace(
|
||||||
"%playerName%",
|
"%playerName%",
|
||||||
params?.playerName ?? "UnknowPlayer",
|
params?.playerName ?? "UnknowPlayer",
|
||||||
);
|
);
|
||||||
newComponent.text = newComponent.text.replace(
|
newComponent.text = newComponent.text.replace(
|
||||||
"%groupName%",
|
"%groupName%",
|
||||||
params?.groupName ?? "UnknowGroup",
|
params?.groupName ?? "UnknowGroup",
|
||||||
);
|
);
|
||||||
newComponent.text = newComponent.text.replace(
|
newComponent.text = newComponent.text.replace(
|
||||||
"%playerPosX%",
|
"%playerPosX%",
|
||||||
params?.info?.x.toString() ?? "UnknowPosX",
|
params?.info?.x.toString() ?? "UnknowPosX",
|
||||||
);
|
);
|
||||||
newComponent.text = newComponent.text.replace(
|
newComponent.text = newComponent.text.replace(
|
||||||
"%playerPosY%",
|
"%playerPosY%",
|
||||||
params?.info?.y.toString() ?? "UnknowPosY",
|
params?.info?.y.toString() ?? "UnknowPosY",
|
||||||
);
|
);
|
||||||
newComponent.text = newComponent.text.replace(
|
newComponent.text = newComponent.text.replace(
|
||||||
"%playerPosZ%",
|
"%playerPosZ%",
|
||||||
params?.info?.z.toString() ?? "UnknowPosZ",
|
params?.info?.z.toString() ?? "UnknowPosZ",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return newComponent;
|
return newComponent;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendMessage(
|
function sendMessage(
|
||||||
toastConfig: ToastConfig,
|
toastConfig: ToastConfig,
|
||||||
targetPlayer: string,
|
targetPlayer: string,
|
||||||
params: ParseParams,
|
params: ParseParams,
|
||||||
) {
|
) {
|
||||||
let releaser = configLock.tryAcquireRead();
|
let releaser = configLock.tryAcquireRead();
|
||||||
while (releaser === undefined) {
|
while (releaser === undefined) {
|
||||||
sleep(0.1);
|
sleep(0.1);
|
||||||
releaser = configLock.tryAcquireRead();
|
releaser = configLock.tryAcquireRead();
|
||||||
}
|
}
|
||||||
|
|
||||||
chatManager.sendMessage({
|
chatManager.sendMessage({
|
||||||
message: safeParseTextComponent(
|
message: safeParseTextComponent(
|
||||||
toastConfig.msg ?? config.welcomeToastConfig.msg,
|
toastConfig.msg ?? config.welcomeToastConfig.msg,
|
||||||
params,
|
params,
|
||||||
),
|
),
|
||||||
prefix: toastConfig.prefix ?? config.welcomeToastConfig.prefix,
|
prefix: toastConfig.prefix ?? config.welcomeToastConfig.prefix,
|
||||||
brackets: toastConfig.brackets ?? config.welcomeToastConfig.brackets,
|
brackets: toastConfig.brackets ?? config.welcomeToastConfig.brackets,
|
||||||
bracketColor:
|
bracketColor:
|
||||||
toastConfig.bracketColor ?? config.welcomeToastConfig.bracketColor,
|
toastConfig.bracketColor ?? config.welcomeToastConfig.bracketColor,
|
||||||
targetPlayer: targetPlayer,
|
targetPlayer: targetPlayer,
|
||||||
utf8Support: true,
|
utf8Support: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
releaser.release();
|
releaser.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendToast(
|
function sendToast(
|
||||||
toastConfig: ToastConfig,
|
toastConfig: ToastConfig,
|
||||||
targetPlayer: string,
|
targetPlayer: string,
|
||||||
params: ParseParams,
|
params: ParseParams,
|
||||||
) {
|
) {
|
||||||
let releaser = configLock.tryAcquireRead();
|
let releaser = configLock.tryAcquireRead();
|
||||||
while (releaser === undefined) {
|
while (releaser === undefined) {
|
||||||
sleep(0.1);
|
sleep(0.1);
|
||||||
releaser = configLock.tryAcquireRead();
|
releaser = configLock.tryAcquireRead();
|
||||||
}
|
}
|
||||||
|
|
||||||
chatManager.sendToast({
|
chatManager.sendToast({
|
||||||
message: safeParseTextComponent(
|
message: safeParseTextComponent(
|
||||||
toastConfig.msg ?? config.welcomeToastConfig.msg,
|
toastConfig.msg ?? config.welcomeToastConfig.msg,
|
||||||
params,
|
params,
|
||||||
),
|
),
|
||||||
title: safeParseTextComponent(
|
title: safeParseTextComponent(
|
||||||
toastConfig.title ?? config.welcomeToastConfig.title,
|
toastConfig.title ?? config.welcomeToastConfig.title,
|
||||||
params,
|
params,
|
||||||
),
|
),
|
||||||
prefix: toastConfig.prefix ?? config.welcomeToastConfig.prefix,
|
prefix: toastConfig.prefix ?? config.welcomeToastConfig.prefix,
|
||||||
brackets: toastConfig.brackets ?? config.welcomeToastConfig.brackets,
|
brackets: toastConfig.brackets ?? config.welcomeToastConfig.brackets,
|
||||||
bracketColor:
|
bracketColor:
|
||||||
toastConfig.bracketColor ?? config.welcomeToastConfig.bracketColor,
|
toastConfig.bracketColor ?? config.welcomeToastConfig.bracketColor,
|
||||||
targetPlayer: targetPlayer,
|
targetPlayer: targetPlayer,
|
||||||
utf8Support: true,
|
utf8Support: true,
|
||||||
});
|
});
|
||||||
releaser.release();
|
releaser.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendNotice(player: string, playerInfo?: PlayerInfo) {
|
function sendNotice(player: string, playerInfo?: PlayerInfo) {
|
||||||
let releaser = configLock.tryAcquireRead();
|
let releaser = configLock.tryAcquireRead();
|
||||||
while (releaser === undefined) {
|
while (releaser === undefined) {
|
||||||
sleep(0.1);
|
sleep(0.1);
|
||||||
releaser = configLock.tryAcquireRead();
|
releaser = configLock.tryAcquireRead();
|
||||||
}
|
}
|
||||||
|
|
||||||
const onlinePlayers = playerDetector.getOnlinePlayers();
|
const onlinePlayers = playerDetector.getOnlinePlayers();
|
||||||
const noticeTargetPlayers = config.adminGroupConfig.groupUsers.concat(
|
const noticeTargetPlayers = config.adminGroupConfig.groupUsers.concat(
|
||||||
config.usersGroups
|
config.usersGroups
|
||||||
.filter((value) => value.isNotice)
|
.filter((value) => value.isNotice)
|
||||||
.flatMap((value) => value.groupUsers ?? []),
|
.flatMap((value) => value.groupUsers ?? []),
|
||||||
);
|
);
|
||||||
logger.debug(`noticeTargetPlayers: ${noticeTargetPlayers.join(", ")}`);
|
logger.debug(`noticeTargetPlayers: ${noticeTargetPlayers.join(", ")}`);
|
||||||
|
|
||||||
for (const targetPlayer of noticeTargetPlayers) {
|
for (const targetPlayer of noticeTargetPlayers) {
|
||||||
if (!onlinePlayers.includes(targetPlayer)) continue;
|
if (!onlinePlayers.includes(targetPlayer)) continue;
|
||||||
sendToast(config.noticeToastConfig, targetPlayer, {
|
sendToast(config.noticeToastConfig, targetPlayer, {
|
||||||
playerName: player,
|
playerName: player,
|
||||||
info: playerInfo,
|
info: playerInfo,
|
||||||
});
|
});
|
||||||
sleep(1);
|
sleep(1);
|
||||||
}
|
}
|
||||||
releaser.release();
|
releaser.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendWarn(player: string) {
|
function sendWarn(player: string) {
|
||||||
const warnMsg = `Not Allowed Player ${player} Break in Home `;
|
const warnMsg = `Not Allowed Player ${player} Break in Home `;
|
||||||
logger.warn(warnMsg);
|
logger.warn(warnMsg);
|
||||||
|
|
||||||
let releaser = configLock.tryAcquireRead();
|
|
||||||
while (releaser === undefined) {
|
|
||||||
sleep(0.1);
|
|
||||||
releaser = configLock.tryAcquireRead();
|
|
||||||
}
|
|
||||||
|
|
||||||
sendToast(config.warnToastConfig, player, { playerName: player });
|
|
||||||
chatManager.sendMessage({
|
|
||||||
message: safeParseTextComponent(config.warnToastConfig.msg, {
|
|
||||||
playerName: player,
|
|
||||||
}),
|
|
||||||
targetPlayer: player,
|
|
||||||
prefix: "AccessControl",
|
|
||||||
brackets: "[]",
|
|
||||||
utf8Support: true,
|
|
||||||
});
|
|
||||||
releaser.release();
|
|
||||||
}
|
|
||||||
|
|
||||||
function watchLoop() {
|
|
||||||
while (gIsRunning) {
|
|
||||||
const releaser = configLock.tryAcquireRead();
|
|
||||||
if (releaser === undefined) {
|
|
||||||
os.sleep(1);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const watchPlayerNames = gWatchPlayersInfo.flatMap((value) => value.name);
|
|
||||||
logger.debug(`Watch Players [ ${watchPlayerNames.join(", ")} ]`);
|
|
||||||
for (const player of gWatchPlayersInfo) {
|
|
||||||
const playerInfo = playerDetector.getPlayerPos(player.name);
|
|
||||||
if (gInRangePlayers.includes(player.name)) {
|
|
||||||
// Notice
|
|
||||||
if (player.hasNoticeTimes < config.noticeTimes) {
|
|
||||||
sendNotice(player.name, playerInfo);
|
|
||||||
player.hasNoticeTimes += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warn
|
|
||||||
if (config.isWarn) sendWarn(player.name);
|
|
||||||
|
|
||||||
// Record
|
|
||||||
logger.warn(
|
|
||||||
`Stranger ${player.name} appear at ${playerInfo?.x}, ${playerInfo?.y}, ${playerInfo?.z}`,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Get rid of player from list
|
|
||||||
gWatchPlayersInfo = gWatchPlayersInfo.filter(
|
|
||||||
(value) => value.name != player.name,
|
|
||||||
);
|
|
||||||
logger.info(
|
|
||||||
`Stranger ${player.name} has left the range at ${playerInfo?.x}, ${playerInfo?.y}, ${playerInfo?.z}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
os.sleep(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
releaser.release();
|
|
||||||
os.sleep(config.watchInterval);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function mainLoop() {
|
|
||||||
while (gIsRunning) {
|
|
||||||
const releaser = configLock.tryAcquireRead();
|
|
||||||
if (releaser === undefined) {
|
|
||||||
os.sleep(0.1);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const players = playerDetector.getPlayersInRange(config.detectRange);
|
|
||||||
const playersList = "[ " + players.join(",") + " ]";
|
|
||||||
logger.debug(`Detected ${players.length} players: ${playersList}`);
|
|
||||||
|
|
||||||
for (const player of players) {
|
|
||||||
if (gInRangePlayers.includes(player)) continue;
|
|
||||||
|
|
||||||
// Get player Info
|
|
||||||
const playerInfo = playerDetector.getPlayerPos(player);
|
|
||||||
|
|
||||||
if (config.adminGroupConfig.groupUsers.includes(player)) {
|
|
||||||
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
|
|
||||||
let groupConfig: UserGroupConfig = {
|
|
||||||
groupName: "Unfamiliar",
|
|
||||||
groupUsers: [],
|
|
||||||
isAllowed: false,
|
|
||||||
isNotice: false,
|
|
||||||
isWelcome: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get user group config
|
|
||||||
for (const userGroupConfig of config.usersGroups) {
|
|
||||||
if (userGroupConfig.groupUsers == undefined) continue;
|
|
||||||
if (!userGroupConfig.groupUsers.includes(player)) continue;
|
|
||||||
|
|
||||||
groupConfig = userGroupConfig;
|
|
||||||
logger.info(
|
|
||||||
`${groupConfig.groupName} ${player} appear at ${playerInfo?.x}, ${playerInfo?.y}, ${playerInfo?.z}`,
|
|
||||||
);
|
|
||||||
if (userGroupConfig.isWelcome)
|
|
||||||
sendMessage(config.welcomeToastConfig, player, {
|
|
||||||
playerName: player,
|
|
||||||
groupName: groupConfig.groupName,
|
|
||||||
info: playerInfo,
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (groupConfig.isAllowed) continue;
|
|
||||||
|
|
||||||
logger.warn(
|
|
||||||
`${groupConfig.groupName} ${player} appear at ${playerInfo?.x}, ${playerInfo?.y}, ${playerInfo?.z}`,
|
|
||||||
);
|
|
||||||
if (config.isWelcome)
|
|
||||||
sendMessage(config.welcomeToastConfig, player, {
|
|
||||||
playerName: player,
|
|
||||||
groupName: groupConfig.groupName,
|
|
||||||
info: playerInfo,
|
|
||||||
});
|
|
||||||
if (config.isWarn) sendWarn(player);
|
|
||||||
gWatchPlayersInfo = [
|
|
||||||
...gWatchPlayersInfo,
|
|
||||||
{ name: player, hasNoticeTimes: 0 },
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
gInRangePlayers = players;
|
|
||||||
releaser.release();
|
|
||||||
os.sleep(config.detectInterval);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function keyboardLoop() {
|
|
||||||
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);
|
|
||||||
launchAccessControlTUI();
|
|
||||||
logger.info("TUI closed, resuming normal operation");
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`TUI error: ${textutils.serialise(error as object)}`);
|
|
||||||
} finally {
|
|
||||||
logger.setInTerminal(true);
|
|
||||||
reloadConfig();
|
|
||||||
}
|
|
||||||
} else if (event.key === keys.r) {
|
|
||||||
reloadConfig();
|
|
||||||
}
|
|
||||||
// else if (event.key === keys.q) {
|
|
||||||
// gIsRunning = false;
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function cliLoop() {
|
|
||||||
let printTargetPlayer: string | undefined;
|
|
||||||
const cli = createAccessControlCli({
|
|
||||||
configFilepath: configFilepath,
|
|
||||||
reloadConfig: () => reloadConfig(),
|
|
||||||
logger: logger,
|
|
||||||
print: (msg) =>
|
|
||||||
chatManager.sendMessage({
|
|
||||||
message: msg,
|
|
||||||
targetPlayer: printTargetPlayer,
|
|
||||||
prefix: "Access Control System",
|
|
||||||
brackets: "[]",
|
|
||||||
utf8Support: true,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
while (gIsRunning) {
|
|
||||||
const result = chatManager.getReceivedMessage();
|
|
||||||
if (result.isErr()) {
|
|
||||||
sleep(0.5);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
logger.debug(`Received message: ${result.value.message}`);
|
|
||||||
|
|
||||||
const ev = result.value;
|
|
||||||
|
|
||||||
let releaser = configLock.tryAcquireRead();
|
let releaser = configLock.tryAcquireRead();
|
||||||
while (releaser === undefined) {
|
while (releaser === undefined) {
|
||||||
sleep(0.1);
|
sleep(0.1);
|
||||||
releaser = configLock.tryAcquireRead();
|
releaser = configLock.tryAcquireRead();
|
||||||
}
|
}
|
||||||
|
|
||||||
const isAdmin = config.adminGroupConfig.groupUsers.includes(ev.username);
|
sendToast(config.warnToastConfig, player, { playerName: player });
|
||||||
|
chatManager.sendMessage({
|
||||||
|
message: safeParseTextComponent(config.warnToastConfig.msg, {
|
||||||
|
playerName: player,
|
||||||
|
}),
|
||||||
|
targetPlayer: player,
|
||||||
|
prefix: "AccessControl",
|
||||||
|
brackets: "[]",
|
||||||
|
utf8Support: true,
|
||||||
|
});
|
||||||
releaser.release();
|
releaser.release();
|
||||||
if (!isAdmin) continue;
|
}
|
||||||
if (!ev.message.startsWith("@AC")) continue;
|
|
||||||
|
|
||||||
printTargetPlayer = ev.username;
|
function watchLoop() {
|
||||||
logger.info(
|
while (gIsRunning) {
|
||||||
`Received command "${ev.message}" from admin ${printTargetPlayer}`,
|
const releaser = configLock.tryAcquireRead();
|
||||||
);
|
if (releaser === undefined) {
|
||||||
|
os.sleep(1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const commandArgs = ev.message
|
const watchPlayerNames = gWatchPlayersInfo.flatMap(
|
||||||
.substring(3)
|
(value) => value.name,
|
||||||
.split(" ")
|
);
|
||||||
.filter((s) => s.length > 0);
|
logger.debug(`Watch Players [ ${watchPlayerNames.join(", ")} ]`);
|
||||||
logger.debug(`Command arguments: ${commandArgs.join(", ")}`);
|
for (const player of gWatchPlayersInfo) {
|
||||||
|
const playerInfo = playerDetector.getPlayerPos(player.name);
|
||||||
|
if (gInRangePlayers.includes(player.name)) {
|
||||||
|
// Notice
|
||||||
|
if (player.hasNoticeTimes < config.noticeTimes) {
|
||||||
|
sendNotice(player.name, playerInfo);
|
||||||
|
player.hasNoticeTimes += 1;
|
||||||
|
}
|
||||||
|
|
||||||
cli(commandArgs);
|
// Warn
|
||||||
printTargetPlayer = undefined;
|
if (config.isWarn) sendWarn(player.name);
|
||||||
}
|
|
||||||
|
// Record
|
||||||
|
logger.warn(
|
||||||
|
`Stranger ${player.name} appear at ${playerInfo?.x}, ${playerInfo?.y}, ${playerInfo?.z}`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Get rid of player from list
|
||||||
|
gWatchPlayersInfo = gWatchPlayersInfo.filter(
|
||||||
|
(value) => value.name != player.name,
|
||||||
|
);
|
||||||
|
logger.info(
|
||||||
|
`Stranger ${player.name} has left the range at ${playerInfo?.x}, ${playerInfo?.y}, ${playerInfo?.z}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
os.sleep(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
releaser.release();
|
||||||
|
os.sleep(config.watchInterval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mainLoop() {
|
||||||
|
while (gIsRunning) {
|
||||||
|
const releaser = configLock.tryAcquireRead();
|
||||||
|
if (releaser === undefined) {
|
||||||
|
os.sleep(0.1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const players = playerDetector.getPlayersInRange(config.detectRange);
|
||||||
|
const playersList = "[ " + players.join(",") + " ]";
|
||||||
|
logger.debug(`Detected ${players.length} players: ${playersList}`);
|
||||||
|
|
||||||
|
for (const player of players) {
|
||||||
|
if (gInRangePlayers.includes(player)) continue;
|
||||||
|
|
||||||
|
// Get player Info
|
||||||
|
const playerInfo = playerDetector.getPlayerPos(player);
|
||||||
|
|
||||||
|
if (config.adminGroupConfig.groupUsers.includes(player)) {
|
||||||
|
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
|
||||||
|
let groupConfig: UserGroupConfig = {
|
||||||
|
groupName: "Unfamiliar",
|
||||||
|
groupUsers: [],
|
||||||
|
isAllowed: false,
|
||||||
|
isNotice: false,
|
||||||
|
isWelcome: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get user group config
|
||||||
|
for (const userGroupConfig of config.usersGroups) {
|
||||||
|
if (userGroupConfig.groupUsers == undefined) continue;
|
||||||
|
if (!userGroupConfig.groupUsers.includes(player)) continue;
|
||||||
|
|
||||||
|
groupConfig = userGroupConfig;
|
||||||
|
logger.info(
|
||||||
|
`${groupConfig.groupName} ${player} appear at ${playerInfo?.x}, ${playerInfo?.y}, ${playerInfo?.z}`,
|
||||||
|
);
|
||||||
|
if (userGroupConfig.isWelcome)
|
||||||
|
sendMessage(config.welcomeToastConfig, player, {
|
||||||
|
playerName: player,
|
||||||
|
groupName: groupConfig.groupName,
|
||||||
|
info: playerInfo,
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupConfig.isAllowed) continue;
|
||||||
|
|
||||||
|
logger.warn(
|
||||||
|
`${groupConfig.groupName} ${player} appear at ${playerInfo?.x}, ${playerInfo?.y}, ${playerInfo?.z}`,
|
||||||
|
);
|
||||||
|
if (config.isWelcome)
|
||||||
|
sendMessage(config.welcomeToastConfig, player, {
|
||||||
|
playerName: player,
|
||||||
|
groupName: groupConfig.groupName,
|
||||||
|
info: playerInfo,
|
||||||
|
});
|
||||||
|
if (config.isWarn) sendWarn(player);
|
||||||
|
gWatchPlayersInfo = [
|
||||||
|
...gWatchPlayersInfo,
|
||||||
|
{ name: player, hasNoticeTimes: 0 },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
gInRangePlayers = players;
|
||||||
|
releaser.release();
|
||||||
|
os.sleep(config.detectInterval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function keyboardLoop() {
|
||||||
|
while (gIsRunning) {
|
||||||
|
const event = pullEventAs(KeyEvent, "key");
|
||||||
|
if (event === undefined) continue;
|
||||||
|
|
||||||
|
if (event.key === keys.c) {
|
||||||
|
logger.info("Launching Access Control TUI...");
|
||||||
|
try {
|
||||||
|
isOnConsoleStream = false;
|
||||||
|
launchAccessControlTUI();
|
||||||
|
logger.info("TUI closed, resuming normal operation");
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(
|
||||||
|
`TUI error: ${textutils.serialise(error as object)}`,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
isOnConsoleStream = true;
|
||||||
|
reloadConfig();
|
||||||
|
}
|
||||||
|
} else if (event.key === keys.r) {
|
||||||
|
reloadConfig();
|
||||||
|
}
|
||||||
|
// else if (event.key === keys.q) {
|
||||||
|
// gIsRunning = false;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cliLoop() {
|
||||||
|
let printTargetPlayer: string | undefined;
|
||||||
|
const cli = createAccessControlCli({
|
||||||
|
configFilepath: configFilepath,
|
||||||
|
reloadConfig: () => reloadConfig(),
|
||||||
|
logger: logger,
|
||||||
|
print: (msg) =>
|
||||||
|
chatManager.sendMessage({
|
||||||
|
message: msg,
|
||||||
|
targetPlayer: printTargetPlayer,
|
||||||
|
prefix: "Access Control System",
|
||||||
|
brackets: "[]",
|
||||||
|
utf8Support: true,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
while (gIsRunning) {
|
||||||
|
const result = chatManager.getReceivedMessage();
|
||||||
|
if (result.isErr()) {
|
||||||
|
sleep(0.5);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
logger.debug(`Received message: ${result.value.message}`);
|
||||||
|
|
||||||
|
const ev = result.value;
|
||||||
|
|
||||||
|
let releaser = configLock.tryAcquireRead();
|
||||||
|
while (releaser === undefined) {
|
||||||
|
sleep(0.1);
|
||||||
|
releaser = configLock.tryAcquireRead();
|
||||||
|
}
|
||||||
|
|
||||||
|
const isAdmin = config.adminGroupConfig.groupUsers.includes(
|
||||||
|
ev.username,
|
||||||
|
);
|
||||||
|
|
||||||
|
releaser.release();
|
||||||
|
if (!isAdmin) continue;
|
||||||
|
if (!ev.message.startsWith("@AC")) continue;
|
||||||
|
|
||||||
|
printTargetPlayer = ev.username;
|
||||||
|
logger.info(
|
||||||
|
`Received command "${ev.message}" from admin ${printTargetPlayer}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const commandArgs = ev.message
|
||||||
|
.substring(3)
|
||||||
|
.split(" ")
|
||||||
|
.filter((s) => s.length > 0);
|
||||||
|
logger.debug(`Command arguments: ${commandArgs.join(", ")}`);
|
||||||
|
|
||||||
|
cli(commandArgs);
|
||||||
|
printTargetPlayer = undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function main(args: string[]) {
|
function main(args: string[]) {
|
||||||
logger.info("Starting access control system, get args: " + args.join(", "));
|
logger.info("Starting access control system, get args: " + args.join(", "));
|
||||||
if (args.length == 1) {
|
if (args.length == 1) {
|
||||||
if (args[0] == "start") {
|
if (args[0] == "start") {
|
||||||
const tutorial: string[] = [];
|
const tutorial: string[] = [];
|
||||||
tutorial.push("Access Control System started.");
|
tutorial.push("Access Control System started.");
|
||||||
tutorial.push("\tPress 'c' to open configuration TUI.");
|
tutorial.push("\tPress 'c' to open configuration TUI.");
|
||||||
tutorial.push("\tPress 'r' to reload configuration.");
|
tutorial.push("\tPress 'r' to reload configuration.");
|
||||||
print(tutorial.join("\n"));
|
print(tutorial.join("\n"));
|
||||||
parallel.waitForAll(
|
parallel.waitForAll(
|
||||||
() => mainLoop(),
|
() => mainLoop(),
|
||||||
() => gTimerManager.run(),
|
() => gTimerManager.run(),
|
||||||
() => cliLoop(),
|
() => cliLoop(),
|
||||||
() => watchLoop(),
|
() => watchLoop(),
|
||||||
() => keyboardLoop(),
|
() => keyboardLoop(),
|
||||||
() => chatManager.run(),
|
() => chatManager.run(),
|
||||||
);
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} else if (args[0] == "config") {
|
} else if (args[0] == "config") {
|
||||||
logger.info("Launching Access Control TUI...");
|
logger.info("Launching Access Control TUI...");
|
||||||
logger.setInTerminal(false);
|
isOnConsoleStream = false;
|
||||||
try {
|
|
||||||
launchAccessControlTUI();
|
try {
|
||||||
} catch (error) {
|
launchAccessControlTUI();
|
||||||
logger.error(`TUI error: ${textutils.serialise(error as object)}`);
|
} catch (error) {
|
||||||
}
|
logger.error(
|
||||||
return;
|
`TUI error: ${textutils.serialise(error as object)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
print(`Usage: accesscontrol start | config`);
|
print(`Usage: accesscontrol start | config`);
|
||||||
print(" start - Start the access control system with monitoring");
|
print(" start - Start the access control system with monitoring");
|
||||||
print(" config - Open configuration TUI");
|
print(" config - Open configuration TUI");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
main(args);
|
main(args);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
logger.error(textutils.serialise(error as object));
|
logger.error(textutils.serialise(error as object));
|
||||||
} finally {
|
} finally {
|
||||||
logger.close();
|
logger.close();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,215 +1,242 @@
|
|||||||
import {
|
import {
|
||||||
CraftManager,
|
CraftManager,
|
||||||
CraftRecipe,
|
CraftRecipe,
|
||||||
CreatePackageTag,
|
CreatePackageTag,
|
||||||
} from "@/lib/CraftManager";
|
} from "@/lib/CraftManager";
|
||||||
import { CCLog, LogLevel } from "@/lib/ccLog";
|
|
||||||
import { Queue } from "@/lib/datatype/Queue";
|
import { Queue } from "@/lib/datatype/Queue";
|
||||||
|
import {
|
||||||
|
ConsoleStream,
|
||||||
|
Logger,
|
||||||
|
LogLevel,
|
||||||
|
processor,
|
||||||
|
textRenderer,
|
||||||
|
} from "@/lib/ccStructLog";
|
||||||
|
|
||||||
const logger = new CCLog("autocraft.log", { outputMinLevel: LogLevel.Info });
|
const logger = new Logger({
|
||||||
|
processors: [
|
||||||
|
processor.filterByLevel(LogLevel.Info),
|
||||||
|
processor.addFullTimestamp(),
|
||||||
|
],
|
||||||
|
renderer: textRenderer,
|
||||||
|
streams: [new ConsoleStream()],
|
||||||
|
});
|
||||||
|
|
||||||
const peripheralsNames = {
|
const peripheralsNames = {
|
||||||
// packsInventory: "minecraft:chest_14",
|
// packsInventory: "minecraft:chest_14",
|
||||||
// itemsInventory: "minecraft:chest_15",
|
// itemsInventory: "minecraft:chest_15",
|
||||||
// packageExtractor: "create:packager_3",
|
// packageExtractor: "create:packager_3",
|
||||||
blockReader: "bottom",
|
blockReader: "bottom",
|
||||||
wiredModem: "right",
|
wiredModem: "right",
|
||||||
redstone: "left",
|
redstone: "left",
|
||||||
packsInventory: "minecraft:chest_1121",
|
packsInventory: "minecraft:chest_1121",
|
||||||
itemsInventory: "minecraft:chest_1120",
|
itemsInventory: "minecraft:chest_1120",
|
||||||
packageExtractor: "create:packager_0",
|
packageExtractor: "create:packager_0",
|
||||||
};
|
};
|
||||||
|
|
||||||
const packsInventory = peripheral.wrap(
|
const packsInventory = peripheral.wrap(
|
||||||
peripheralsNames.packsInventory,
|
peripheralsNames.packsInventory,
|
||||||
) as InventoryPeripheral;
|
) as InventoryPeripheral;
|
||||||
const itemsInventory = peripheral.wrap(
|
const itemsInventory = peripheral.wrap(
|
||||||
peripheralsNames.itemsInventory,
|
peripheralsNames.itemsInventory,
|
||||||
) as InventoryPeripheral;
|
) as InventoryPeripheral;
|
||||||
const packageExtractor = peripheral.wrap(
|
const packageExtractor = peripheral.wrap(
|
||||||
peripheralsNames.packageExtractor,
|
peripheralsNames.packageExtractor,
|
||||||
) as InventoryPeripheral;
|
) as InventoryPeripheral;
|
||||||
const blockReader = peripheral.wrap(
|
const blockReader = peripheral.wrap(
|
||||||
peripheralsNames.blockReader,
|
peripheralsNames.blockReader,
|
||||||
) as BlockReaderPeripheral;
|
) as BlockReaderPeripheral;
|
||||||
const wiredModem = peripheral.wrap(
|
const wiredModem = peripheral.wrap(
|
||||||
peripheralsNames.wiredModem,
|
peripheralsNames.wiredModem,
|
||||||
) as WiredModemPeripheral;
|
) as WiredModemPeripheral;
|
||||||
const turtleLocalName = wiredModem.getNameLocal();
|
const turtleLocalName = wiredModem.getNameLocal();
|
||||||
|
|
||||||
enum State {
|
enum State {
|
||||||
IDLE,
|
IDLE,
|
||||||
READ_RECIPE,
|
READ_RECIPE,
|
||||||
CRAFT_OUTPUT,
|
CRAFT_OUTPUT,
|
||||||
}
|
}
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
const craftManager = new CraftManager(turtleLocalName, itemsInventory);
|
const craftManager = new CraftManager(turtleLocalName, itemsInventory);
|
||||||
const recipesQueue = new Queue<CraftRecipe>();
|
const recipesQueue = new Queue<CraftRecipe>();
|
||||||
const recipesWaitingMap = new Map<number, CraftRecipe[] | CraftRecipe>();
|
const recipesWaitingMap = new Map<number, CraftRecipe[] | CraftRecipe>();
|
||||||
let currentState = State.IDLE;
|
let currentState = State.IDLE;
|
||||||
let nextState = State.IDLE;
|
let nextState = State.IDLE;
|
||||||
let hasPackage = redstone.getInput(peripheralsNames.redstone);
|
let hasPackage = redstone.getInput(peripheralsNames.redstone);
|
||||||
while (hasPackage) {
|
while (hasPackage) {
|
||||||
hasPackage = redstone.getInput(peripheralsNames.redstone);
|
|
||||||
logger.warn("redstone activated when init, please clear inventory");
|
|
||||||
sleep(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info("AutoCraft init finished...");
|
|
||||||
while (true) {
|
|
||||||
// Switch state
|
|
||||||
switch (currentState) {
|
|
||||||
case State.IDLE: {
|
|
||||||
nextState = hasPackage ? State.READ_RECIPE : State.IDLE;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case State.READ_RECIPE: {
|
|
||||||
nextState = hasPackage ? State.READ_RECIPE : State.CRAFT_OUTPUT;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case State.CRAFT_OUTPUT: {
|
|
||||||
nextState =
|
|
||||||
recipesQueue.size() > 0
|
|
||||||
? State.CRAFT_OUTPUT
|
|
||||||
: hasPackage
|
|
||||||
? State.READ_RECIPE
|
|
||||||
: State.IDLE;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
logger.error(`Unknown state`);
|
|
||||||
nextState = hasPackage ? State.READ_RECIPE : State.IDLE;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// State logic
|
|
||||||
switch (currentState) {
|
|
||||||
case State.IDLE: {
|
|
||||||
if (!hasPackage) os.pullEvent("redstone");
|
|
||||||
hasPackage = redstone.getInput(peripheralsNames.redstone);
|
hasPackage = redstone.getInput(peripheralsNames.redstone);
|
||||||
break;
|
logger.warn("redstone activated when init, please clear inventory");
|
||||||
}
|
|
||||||
|
|
||||||
case State.READ_RECIPE: {
|
|
||||||
logger.info(`Package detected`);
|
|
||||||
const packagesInfoRecord = packsInventory.list();
|
|
||||||
for (const key in packagesInfoRecord) {
|
|
||||||
const slotNum = parseInt(key);
|
|
||||||
packsInventory.pushItems(turtleLocalName, slotNum);
|
|
||||||
|
|
||||||
// Get package NBT
|
|
||||||
logger.debug(
|
|
||||||
`Turtle:\n${textutils.serialise(blockReader.getBlockData()!, { allow_repetitions: true })}`,
|
|
||||||
);
|
|
||||||
const packageDetailInfo = blockReader.getBlockData()?.Items[1];
|
|
||||||
if (packageDetailInfo === undefined) {
|
|
||||||
logger.error(`Package detail info not found`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get OrderId and isFinal
|
|
||||||
const packageOrderId = (packageDetailInfo.tag as CreatePackageTag)
|
|
||||||
.Fragment.OrderId;
|
|
||||||
const packageIsFinal =
|
|
||||||
(packageDetailInfo.tag as CreatePackageTag).Fragment.IsFinal > 0
|
|
||||||
? true
|
|
||||||
: false;
|
|
||||||
|
|
||||||
// Get recipe
|
|
||||||
const packageRecipes =
|
|
||||||
CraftManager.getPackageRecipe(packageDetailInfo);
|
|
||||||
if (packageRecipes.isSome()) {
|
|
||||||
if (packageIsFinal) recipesQueue.enqueue(packageRecipes.value);
|
|
||||||
else recipesWaitingMap.set(packageOrderId, packageRecipes.value);
|
|
||||||
} else {
|
|
||||||
if (packageIsFinal && recipesWaitingMap.has(packageOrderId)) {
|
|
||||||
recipesQueue.enqueue(recipesWaitingMap.get(packageOrderId)!);
|
|
||||||
recipesWaitingMap.delete(packageOrderId);
|
|
||||||
} else {
|
|
||||||
logger.debug(`No recipe, just pass`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
packageExtractor.pullItems(turtleLocalName, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
currentState === State.READ_RECIPE &&
|
|
||||||
nextState === State.CRAFT_OUTPUT
|
|
||||||
) {
|
|
||||||
craftManager.initItemsMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case State.CRAFT_OUTPUT: {
|
|
||||||
// Check recipe
|
|
||||||
const recipe = recipesQueue.dequeue();
|
|
||||||
if (recipe === undefined) break;
|
|
||||||
|
|
||||||
let restCraftCnt = recipe.Count;
|
|
||||||
let maxSignleCraftCnt = restCraftCnt;
|
|
||||||
|
|
||||||
let craftItemDetail: ItemDetail | undefined = undefined;
|
|
||||||
do {
|
|
||||||
// Clear workbench
|
|
||||||
craftManager.clearTurtle();
|
|
||||||
|
|
||||||
logger.info(`Pull items according to a recipe`);
|
|
||||||
const craftCnt = craftManager
|
|
||||||
.pullItemsWithRecipe(recipe, maxSignleCraftCnt)
|
|
||||||
.unwrapOrElse((error) => {
|
|
||||||
logger.error(error.message);
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (craftCnt == 0) break;
|
|
||||||
if (craftCnt < maxSignleCraftCnt) maxSignleCraftCnt = craftCnt;
|
|
||||||
const craftRet = craftManager.craft(maxSignleCraftCnt);
|
|
||||||
craftItemDetail ??= craftRet;
|
|
||||||
logger.info(`Craft ${craftCnt} times`);
|
|
||||||
restCraftCnt -= craftCnt;
|
|
||||||
} while (restCraftCnt > 0);
|
|
||||||
|
|
||||||
// Finally output
|
|
||||||
if (restCraftCnt > 0) {
|
|
||||||
logger.warn(
|
|
||||||
`Only craft ${recipe.Count - restCraftCnt}x ${craftItemDetail?.name ?? "UnknownItem"}`,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
logger.info(
|
|
||||||
`Finish craft ${recipe.Count}x ${craftItemDetail?.name ?? "UnknownItem"}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear workbench and inventory
|
|
||||||
const turtleItemSlots = Object.values(
|
|
||||||
blockReader.getBlockData()!.Items,
|
|
||||||
).map((val) => val.Slot + 1);
|
|
||||||
craftManager.clearTurtle(turtleItemSlots);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default: {
|
|
||||||
sleep(1);
|
sleep(1);
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check packages
|
logger.info("AutoCraft init finished...");
|
||||||
hasPackage = redstone.getInput(peripheralsNames.redstone);
|
while (true) {
|
||||||
// State update
|
// Switch state
|
||||||
currentState = nextState;
|
switch (currentState) {
|
||||||
}
|
case State.IDLE: {
|
||||||
|
nextState = hasPackage ? State.READ_RECIPE : State.IDLE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case State.READ_RECIPE: {
|
||||||
|
nextState = hasPackage ? State.READ_RECIPE : State.CRAFT_OUTPUT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case State.CRAFT_OUTPUT: {
|
||||||
|
nextState =
|
||||||
|
recipesQueue.size() > 0
|
||||||
|
? State.CRAFT_OUTPUT
|
||||||
|
: hasPackage
|
||||||
|
? State.READ_RECIPE
|
||||||
|
: State.IDLE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
logger.error(`Unknown state`);
|
||||||
|
nextState = hasPackage ? State.READ_RECIPE : State.IDLE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// State logic
|
||||||
|
switch (currentState) {
|
||||||
|
case State.IDLE: {
|
||||||
|
if (!hasPackage) os.pullEvent("redstone");
|
||||||
|
hasPackage = redstone.getInput(peripheralsNames.redstone);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case State.READ_RECIPE: {
|
||||||
|
logger.info(`Package detected`);
|
||||||
|
const packagesInfoRecord = packsInventory.list();
|
||||||
|
for (const key in packagesInfoRecord) {
|
||||||
|
const slotNum = parseInt(key);
|
||||||
|
packsInventory.pushItems(turtleLocalName, slotNum);
|
||||||
|
|
||||||
|
// Get package NBT
|
||||||
|
logger.debug(
|
||||||
|
`Turtle:\n${textutils.serialise(blockReader.getBlockData()!, { allow_repetitions: true })}`,
|
||||||
|
);
|
||||||
|
const packageDetailInfo =
|
||||||
|
blockReader.getBlockData()?.Items[1];
|
||||||
|
if (packageDetailInfo === undefined) {
|
||||||
|
logger.error(`Package detail info not found`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get OrderId and isFinal
|
||||||
|
const packageOrderId = (
|
||||||
|
packageDetailInfo.tag as CreatePackageTag
|
||||||
|
).Fragment.OrderId;
|
||||||
|
const packageIsFinal =
|
||||||
|
(packageDetailInfo.tag as CreatePackageTag).Fragment
|
||||||
|
.IsFinal > 0
|
||||||
|
? true
|
||||||
|
: false;
|
||||||
|
|
||||||
|
// Get recipe
|
||||||
|
const packageRecipes =
|
||||||
|
CraftManager.getPackageRecipe(packageDetailInfo);
|
||||||
|
if (packageRecipes.isSome()) {
|
||||||
|
if (packageIsFinal)
|
||||||
|
recipesQueue.enqueue(packageRecipes.value);
|
||||||
|
else
|
||||||
|
recipesWaitingMap.set(
|
||||||
|
packageOrderId,
|
||||||
|
packageRecipes.value,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (
|
||||||
|
packageIsFinal &&
|
||||||
|
recipesWaitingMap.has(packageOrderId)
|
||||||
|
) {
|
||||||
|
recipesQueue.enqueue(
|
||||||
|
recipesWaitingMap.get(packageOrderId)!,
|
||||||
|
);
|
||||||
|
recipesWaitingMap.delete(packageOrderId);
|
||||||
|
} else {
|
||||||
|
logger.debug(`No recipe, just pass`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
packageExtractor.pullItems(turtleLocalName, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
currentState === State.READ_RECIPE &&
|
||||||
|
nextState === State.CRAFT_OUTPUT
|
||||||
|
) {
|
||||||
|
craftManager.initItemsMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case State.CRAFT_OUTPUT: {
|
||||||
|
// Check recipe
|
||||||
|
const recipe = recipesQueue.dequeue();
|
||||||
|
if (recipe === undefined) break;
|
||||||
|
|
||||||
|
let restCraftCnt = recipe.Count;
|
||||||
|
let maxSignleCraftCnt = restCraftCnt;
|
||||||
|
|
||||||
|
let craftItemDetail: ItemDetail | undefined = undefined;
|
||||||
|
do {
|
||||||
|
// Clear workbench
|
||||||
|
craftManager.clearTurtle();
|
||||||
|
|
||||||
|
logger.info(`Pull items according to a recipe`);
|
||||||
|
const craftCnt = craftManager
|
||||||
|
.pullItemsWithRecipe(recipe, maxSignleCraftCnt)
|
||||||
|
.unwrapOrElse((error) => {
|
||||||
|
logger.error(error.message);
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (craftCnt == 0) break;
|
||||||
|
if (craftCnt < maxSignleCraftCnt)
|
||||||
|
maxSignleCraftCnt = craftCnt;
|
||||||
|
const craftRet = craftManager.craft(maxSignleCraftCnt);
|
||||||
|
craftItemDetail ??= craftRet;
|
||||||
|
logger.info(`Craft ${craftCnt} times`);
|
||||||
|
restCraftCnt -= craftCnt;
|
||||||
|
} while (restCraftCnt > 0);
|
||||||
|
|
||||||
|
// Finally output
|
||||||
|
if (restCraftCnt > 0) {
|
||||||
|
logger.warn(
|
||||||
|
`Only craft ${recipe.Count - restCraftCnt}x ${craftItemDetail?.name ?? "UnknownItem"}`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
logger.info(
|
||||||
|
`Finish craft ${recipe.Count}x ${craftItemDetail?.name ?? "UnknownItem"}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear workbench and inventory
|
||||||
|
const turtleItemSlots = Object.values(
|
||||||
|
blockReader.getBlockData()!.Items,
|
||||||
|
).map((val) => val.Slot + 1);
|
||||||
|
craftManager.clearTurtle(turtleItemSlots);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
sleep(1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check packages
|
||||||
|
hasPackage = redstone.getInput(peripheralsNames.redstone);
|
||||||
|
// State update
|
||||||
|
currentState = nextState;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
main();
|
main();
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
logger.error(textutils.serialise(error as object));
|
logger.error(textutils.serialise(error as object));
|
||||||
} finally {
|
} finally {
|
||||||
logger.close();
|
logger.close();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,56 +7,16 @@
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
Logger,
|
Logger,
|
||||||
createDevLogger,
|
processor,
|
||||||
createProdLogger,
|
|
||||||
|
|
||||||
// Processors
|
|
||||||
addTimestamp,
|
|
||||||
addFormattedTimestamp,
|
|
||||||
addFullTimestamp,
|
|
||||||
addSource,
|
|
||||||
addComputerId,
|
|
||||||
addStaticFields,
|
|
||||||
transformField,
|
|
||||||
|
|
||||||
// Renderers
|
|
||||||
textRenderer,
|
|
||||||
jsonRenderer,
|
|
||||||
|
|
||||||
// Streams
|
|
||||||
ConsoleStream,
|
ConsoleStream,
|
||||||
FileStream,
|
FileStream,
|
||||||
BufferStream,
|
BufferStream,
|
||||||
DAY,
|
ConditionalStream,
|
||||||
HOUR,
|
HOUR,
|
||||||
|
jsonRenderer,
|
||||||
LogLevel,
|
LogLevel,
|
||||||
|
textRenderer,
|
||||||
} from "../lib/ccStructLog";
|
} from "../lib/ccStructLog";
|
||||||
import { ConditionalStream } from "@/lib/ccStructLog/streams";
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Basic Usage Examples
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
print("=== Basic Usage Examples ===");
|
|
||||||
|
|
||||||
// 1. Quick start with pre-configured loggers
|
|
||||||
const devLog = createDevLogger();
|
|
||||||
devLog.info("Application started", { version: "1.0.0", port: 8080 });
|
|
||||||
devLog.debug("Debug information", { userId: 123, action: "login" });
|
|
||||||
devLog.error("Something went wrong", {
|
|
||||||
error: "Connection failed",
|
|
||||||
retries: 3,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 2. Production logging to file
|
|
||||||
const prodLog = createProdLogger("app.log", {
|
|
||||||
source: "MyApplication",
|
|
||||||
rotationInterval: DAY,
|
|
||||||
includeConsole: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
prodLog.info("User action", { userId: 456, action: "purchase", amount: 29.99 });
|
|
||||||
prodLog.warn("Low disk space", { available: 1024, threshold: 2048 });
|
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Custom Logger Configurations
|
// Custom Logger Configurations
|
||||||
@@ -67,10 +27,10 @@ print("\n=== Custom Logger Configurations ===");
|
|||||||
// 4. Custom logger with specific processors and renderer
|
// 4. Custom logger with specific processors and renderer
|
||||||
const customLogger = new Logger({
|
const customLogger = new Logger({
|
||||||
processors: [
|
processors: [
|
||||||
addFullTimestamp(),
|
processor.addTimestamp(),
|
||||||
addComputerId(),
|
processor.addComputerId(),
|
||||||
addSource("CustomApp"),
|
processor.addSource("CustomApp"),
|
||||||
addStaticFields({
|
processor.addStaticFields({
|
||||||
environment: "development",
|
environment: "development",
|
||||||
version: "2.1.0",
|
version: "2.1.0",
|
||||||
}),
|
}),
|
||||||
@@ -109,10 +69,10 @@ const sanitizePasswords = (event: Map<string, unknown>) => {
|
|||||||
|
|
||||||
const secureLogger = new Logger({
|
const secureLogger = new Logger({
|
||||||
processors: [
|
processors: [
|
||||||
addTimestamp(),
|
processor.addTimestamp(),
|
||||||
addRequestId,
|
addRequestId,
|
||||||
sanitizePasswords,
|
sanitizePasswords,
|
||||||
transformField("message", (msg) => `[SECURE] ${msg}`),
|
processor.transformField("message", (msg) => `[SECURE] ${msg}`),
|
||||||
],
|
],
|
||||||
renderer: jsonRenderer,
|
renderer: jsonRenderer,
|
||||||
streams: [new ConsoleStream()],
|
streams: [new ConsoleStream()],
|
||||||
@@ -133,7 +93,7 @@ print("\n=== Stream Examples ===");
|
|||||||
// 11. Buffer stream for batch processing
|
// 11. Buffer stream for batch processing
|
||||||
const bufferStream = new BufferStream(100); // Keep last 100 messages
|
const bufferStream = new BufferStream(100); // Keep last 100 messages
|
||||||
const bufferLogger = new Logger({
|
const bufferLogger = new Logger({
|
||||||
processors: [addFormattedTimestamp()],
|
processors: [processor.addTimestamp()],
|
||||||
renderer: textRenderer,
|
renderer: textRenderer,
|
||||||
streams: [
|
streams: [
|
||||||
new ConditionalStream(new ConsoleStream(), (msg, event) => {
|
new ConditionalStream(new ConsoleStream(), (msg, event) => {
|
||||||
@@ -162,7 +122,7 @@ for (const msg of bufferedMessages) {
|
|||||||
|
|
||||||
// 12. Multi-stream with different formats
|
// 12. Multi-stream with different formats
|
||||||
const multiFormatLogger = new Logger({
|
const multiFormatLogger = new Logger({
|
||||||
processors: [addFullTimestamp(), addComputerId()],
|
processors: [processor.addTimestamp(), processor.addComputerId()],
|
||||||
renderer: (event) => "default", // This won't be used
|
renderer: (event) => "default", // This won't be used
|
||||||
streams: [
|
streams: [
|
||||||
// Console with human-readable format
|
// Console with human-readable format
|
||||||
@@ -196,7 +156,7 @@ print("\n=== Error Handling Examples ===");
|
|||||||
// 13. Robust error handling
|
// 13. Robust error handling
|
||||||
const robustLogger = new Logger({
|
const robustLogger = new Logger({
|
||||||
processors: [
|
processors: [
|
||||||
addTimestamp(),
|
processor.addTimestamp(),
|
||||||
// Processor that might fail
|
// Processor that might fail
|
||||||
(event) => {
|
(event) => {
|
||||||
try {
|
try {
|
||||||
@@ -231,7 +191,7 @@ print("\n=== Cleanup Examples ===");
|
|||||||
|
|
||||||
// 14. Proper cleanup
|
// 14. Proper cleanup
|
||||||
const fileLogger = new Logger({
|
const fileLogger = new Logger({
|
||||||
processors: [addTimestamp()],
|
processors: [processor.addTimestamp()],
|
||||||
renderer: jsonRenderer,
|
renderer: jsonRenderer,
|
||||||
streams: [new FileStream("temp.log")],
|
streams: [new FileStream("temp.log")],
|
||||||
});
|
});
|
||||||
@@ -249,37 +209,3 @@ print("- all.log (complete log)");
|
|||||||
print("- debug.log (detailed debug info)");
|
print("- debug.log (detailed debug info)");
|
||||||
print("- structured.log (JSON format)");
|
print("- structured.log (JSON format)");
|
||||||
print("- temp.log (temporary file, now closed)");
|
print("- temp.log (temporary file, now closed)");
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// Performance Comparison (commented out to avoid noise)
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
/*
|
|
||||||
print("\n=== Performance Comparison ===");
|
|
||||||
|
|
||||||
const iterations = 1000;
|
|
||||||
|
|
||||||
// Test simple console logging
|
|
||||||
const startTime1 = os.clock();
|
|
||||||
const simpleLogger = createMinimalLogger();
|
|
||||||
for (let i = 0; i < iterations; i++) {
|
|
||||||
simpleLogger.info(`Simple message ${i}`);
|
|
||||||
}
|
|
||||||
const endTime1 = os.clock();
|
|
||||||
print(`Simple Console Logger: ${endTime1 - startTime1} seconds`);
|
|
||||||
|
|
||||||
// Test complex processor chain
|
|
||||||
const startTime2 = os.clock();
|
|
||||||
const complexLogger = createDetailedLogger("perf_test.log", {
|
|
||||||
source: "PerfTest"
|
|
||||||
});
|
|
||||||
for (let i = 0; i < iterations; i++) {
|
|
||||||
complexLogger.info(`Complex message ${i}`, {
|
|
||||||
iteration: i,
|
|
||||||
data: { nested: { value: i * 2 } }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
complexLogger.close();
|
|
||||||
const endTime2 = os.clock();
|
|
||||||
print(`Complex Processor Chain: ${endTime2 - startTime2} seconds`);
|
|
||||||
*/
|
|
||||||
|
|||||||
Reference in New Issue
Block a user