Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --[[
- LagBot Turtle Script v1.1
- Monitors server TPS via a direct redstone signal and provides alerts.
- Also reports the redstone signal (TPS value) on command.
- Changes:
- - (v1.0) Initial concept with peripheral assumption.
- - (v1.1) Adapted to read TPS from a direct redstone signal using the redstone API.
- Removed TPS_METER_PERIPHERAL_NAME.
- Added TPS_INPUT_SIDE configuration.
- Power reporting now reports the direct redstone (TPS) input.
- ]]
- --#region Configuration
- local CHAT_BOX_PERIPHERAL_NAME = "chatBox"
- local TPS_INPUT_SIDE = "front" -- CHANGE THIS to the side the turtle is facing the TPS meter block
- -- Valid sides: "top", "bottom", "left", "right", "front", "back"
- local COMMAND_PREFIX = "@lag"
- local CHAT_BOT_NAME = "LagBot"
- local CHAT_BOT_BRACKETS = "[]"
- local CHAT_BOT_BRACKET_COLOR = "&c" -- Red color for brackets
- -- TPS alert thresholds (triggers if Redstone Signal strength <= value)
- -- Assuming 0-15 Redstone signal directly maps to a scaled TPS value.
- -- For example, if 15 RS = 20 TPS, then 11 RS ~ 15 TPS, 7 RS ~ 10 TPS, 3 RS ~ 5 TPS.
- -- You'll need to adjust these based on how your TPS meter block scales its output.
- local DEFAULT_TPS_ALERT_THRESHOLDS = {3, 2, 1} -- Example: Alert if RS signal <= 3 (e.g. ~5 TPS)
- local TPS_RECOVERY_THRESHOLD_RS = 7 -- Example: Recover if RS signal > 7 (e.g. >10 TPS)
- -- This is the REDSTONE SIGNAL value for recovery.
- local TPS_CHECK_INTERVAL_SECONDS = 5
- local TPS_REPORT_INTERVAL_SECONDS = 2 -- Renamed from POWER_REPORT for clarity
- local DEBUG_MODE = false
- local DEBUG_LOG_FILE = "lagbot.log"
- --#endregion
- --#region Peripherals
- local chatBox = peripheral.find(CHAT_BOX_PERIPHERAL_NAME)
- -- No tpsMeter peripheral needed anymore
- --#endregion
- --#region Debug Logger
- local function logDebug(message)
- if not DEBUG_MODE then return end
- local logMessage = string.format("[%s] %s\n", os.date("%Y-%m-%d %H:%M:%S"), message)
- local file, err = fs.open(DEBUG_LOG_FILE, "a")
- if file then
- file.write(logMessage)
- file.close()
- else
- print("DEBUG LOG ERROR: Could not open " .. DEBUG_LOG_FILE .. ": " .. (err or "unknown error"))
- end
- end
- --#endregion
- --#region State Variables
- logDebug("Script initializing state variables...")
- local currentTpsAlertThresholdsRS = {} -- Stores Redstone Signal thresholds
- for _, v in ipairs(DEFAULT_TPS_ALERT_THRESHOLDS) do table.insert(currentTpsAlertThresholdsRS, v) end
- table.sort(currentTpsAlertThresholdsRS, function(a,b) return a > b end) -- Ensure sorted descending
- local isMuted = false
- local lastNotifiedTpsLevelRS = 16 -- Stores the RS threshold that last triggered (initially > 15)
- local tpsCheckTimerId = nil
- local isReportingTps = false -- Renamed from isReportingPower
- local tpsReportTimerId = nil -- Renamed from powerReportTimerId
- logDebug("State variables initialized. Muted: " .. tostring(isMuted))
- --#endregion
- --#region Minecraft JSON Text Component Colors
- local COLORS = {
- BLACK = "black", DARK_BLUE = "dark_blue", DARK_GREEN = "dark_green", DARK_AQUA = "dark_aqua",
- DARK_RED = "dark_red", DARK_PURPLE = "dark_purple", GOLD = "gold", GRAY = "gray",
- DARK_GRAY = "dark_gray", BLUE = "blue", GREEN = "green", AQUA = "aqua", RED = "red",
- LIGHT_PURPLE = "light_purple", YELLOW = "yellow", WHITE = "white", RESET = "reset"
- }
- --#endregion
- --#region Helper Functions
- local function sendFormattedChat(messageComponents, recipientUsername)
- logDebug("Attempting to send formatted chat. Recipient: " .. (recipientUsername or "ALL"))
- if not chatBox then
- local plainText = ""
- for _, comp in ipairs(messageComponents) do plainText = plainText .. (comp.text or "") end
- local noChatMsg = "[" .. CHAT_BOT_NAME .. "-NoChatBox" .. (recipientUsername and (" to " .. recipientUsername) or "") .. "] " .. plainText
- print(noChatMsg)
- logDebug("ChatBox not found. Printed to console: " .. noChatMsg)
- return
- end
- local jsonMessage = textutils.serialiseJSON(messageComponents)
- if not jsonMessage then
- local fallbackMsg = "Error: Could not serialize message for formatted sending."
- logDebug("JSON Serialization Error. Fallback: " .. fallbackMsg .. " Data: " .. textutils.serialize(messageComponents, {max_depth=2}))
- if recipientUsername then
- chatBox.sendMessageToPlayer(fallbackMsg, recipientUsername, CHAT_BOT_NAME, CHAT_BOT_BRACKETS, CHAT_BOT_BRACKET_COLOR)
- else
- chatBox.sendMessage(fallbackMsg, CHAT_BOT_NAME, CHAT_BOT_BRACKETS, CHAT_BOT_BRACKET_COLOR)
- end
- return
- end
- local success, err
- if recipientUsername then
- success, err = chatBox.sendFormattedMessageToPlayer(jsonMessage, recipientUsername, CHAT_BOT_NAME, CHAT_BOT_BRACKETS, CHAT_BOT_BRACKET_COLOR)
- else
- success, err = chatBox.sendFormattedMessage(jsonMessage, CHAT_BOT_NAME, CHAT_BOT_BRACKETS, CHAT_BOT_BRACKET_COLOR)
- end
- if not success then
- logDebug("Error sending formatted message: " .. (err or "Unknown error") .. ". Attempting fallback to plain text.")
- local plainText = ""
- for _, comp in ipairs(messageComponents) do plainText = plainText .. (comp.text or "") end
- if recipientUsername then
- chatBox.sendMessageToPlayer(plainText, recipientUsername, CHAT_BOT_NAME, CHAT_BOT_BRACKETS, CHAT_BOT_BRACKET_COLOR)
- else
- chatBox.sendMessage(plainText, CHAT_BOT_NAME, CHAT_BOT_BRACKETS, CHAT_BOT_BRACKET_COLOR)
- end
- else
- logDebug("Formatted message sent successfully.")
- end
- os.sleep(0.3)
- end
- local function announce(messageComponents)
- sendFormattedChat(messageComponents)
- end
- local function getCurrentRedstoneTps()
- local success, value = pcall(redstone.getAnalogInput, TPS_INPUT_SIDE)
- if success and type(value) == "number" then
- logDebug("Read Redstone TPS from " .. TPS_INPUT_SIDE .. ": " .. value)
- return value
- else
- logDebug("Failed to read Redstone TPS from " .. TPS_INPUT_SIDE .. ". Error/Value: " .. tostring(value))
- return nil
- end
- end
- local function checkTpsAndAlert()
- logDebug("Checking Redstone TPS...")
- local currentRSTps = getCurrentRedstoneTps()
- if currentRSTps == nil then
- logDebug("Could not retrieve Redstone TPS for alert check.")
- return
- end
- -- Optional: Convert Redstone signal (0-15) to actual TPS (0-20) for display
- -- Example scaling: actualTPS = currentRSTps * (20/15)
- local displayTPS = string.format("%d RS", currentRSTps) -- Default to showing Redstone Signal
- -- If you want to show estimated actual TPS:
- -- local estimatedActualTps = currentRSTps * (20/15)
- -- displayTPS = string.format("%.1f TPS (RS: %d)", estimatedActualTps, currentRSTps)
- logDebug(string.format("Current Redstone TPS: %d, Last Notified RS Level: %s, Muted: %s", currentRSTps, tostring(lastNotifiedTpsLevelRS), tostring(isMuted)))
- if not isMuted then
- local alertTriggeredThisCheck = false
- for _, thresholdRS in ipairs(currentTpsAlertThresholdsRS) do -- Assumes sorted descending
- if currentRSTps <= thresholdRS then
- if thresholdRS < lastNotifiedTpsLevelRS then
- announce({
- {text="WARNING: Server health indicator dropped to ", color=COLORS.YELLOW},
- {text=displayTPS, color=COLORS.RED, bold=true},
- {text=" (Alert Level: <=" .. thresholdRS .. " RS)", color=COLORS.YELLOW}
- })
- lastNotifiedTpsLevelRS = thresholdRS
- alertTriggeredThisCheck = true
- logDebug("Alert sent for RS TPS <= " .. thresholdRS .. ". New lastNotifiedTpsLevelRS: " .. lastNotifiedTpsLevelRS)
- break
- else
- logDebug("RS TPS is " .. currentRSTps .. ", which is <= threshold " .. thresholdRS .. ", but lastNotifiedTpsLevelRS (" .. lastNotifiedTpsLevelRS .. ") is not higher. No new alert.")
- break
- end
- end
- end
- if not alertTriggeredThisCheck and currentRSTps > TPS_RECOVERY_THRESHOLD_RS and lastNotifiedTpsLevelRS <= TPS_RECOVERY_THRESHOLD_RS then
- announce({
- {text="RECOVERY: Server health indicator is now ", color=COLORS.GREEN},
- {text=displayTPS, color=COLORS.AQUA, bold=true},
- {text=" (Above recovery RS threshold of " .. TPS_RECOVERY_THRESHOLD_RS .. ")", color=COLORS.GREEN}
- })
- lastNotifiedTpsLevelRS = 16
- logDebug("Recovery message sent. RS TPS: " .. currentRSTps .. ". lastNotifiedTpsLevelRS reset.")
- end
- else
- logDebug("Alerts are muted. No TPS alert action taken.")
- end
- end
- local function reportCurrentTps() -- Renamed from reportPowerLevel
- if not isReportingTps then return end
- logDebug("Reporting current Redstone TPS...")
- local currentRSTps = getCurrentRedstoneTps()
- if currentRSTps ~= nil then
- -- Optional: Convert to display actual TPS as well
- -- local estimatedActualTps = currentRSTps * (20/15)
- -- local displayTPS = string.format("%.1f TPS (Redstone Signal: %d)", estimatedActualTps, currentRSTps)
- local displayTPS = string.format("%d", currentRSTps) -- Default: just show RS value
- announce({{text="Current Health Indicator (Redstone Signal): ", color=COLORS.GRAY}, {text=displayTPS, color=COLORS.YELLOW}})
- else
- logDebug("Failed to get Redstone TPS for reporting.")
- announce({{text="Error reading health indicator (Redstone Signal).", color=COLORS.RED}})
- isReportingTps = false -- Stop trying
- if tpsReportTimerId then os.cancelTimer(tpsReportTimerId); tpsReportTimerId = nil; end
- return
- end
- if isReportingTps then
- tpsReportTimerId = os.startTimer(TPS_REPORT_INTERVAL_SECONDS)
- logDebug("TPS report timer restarted. ID: " .. (tpsReportTimerId or "nil"))
- else
- logDebug("TPS reporting was stopped, not restarting timer.")
- end
- end
- local function startTpsCheckTimer()
- if tpsCheckTimerId then os.cancelTimer(tpsCheckTimerId) end
- tpsCheckTimerId = os.startTimer(TPS_CHECK_INTERVAL_SECONDS)
- logDebug("TPS check timer started/restarted. ID: " .. tpsCheckTimerId)
- end
- local function startTpsReportTimer() -- Renamed from startPowerReportTimer
- if not isReportingTps then return end
- if tpsReportTimerId then os.cancelTimer(tpsReportTimerId) end
- reportCurrentTps() -- Report immediately, then start timer
- end
- --#endregion
- --#region Command Handlers
- local commandHandlers = {}
- commandHandlers.help = function(username, _)
- logDebug("Executing command: help, User: " .. username)
- announce({{text = "--- LagBot Commands (".. COMMAND_PREFIX .. ") ---", color = COLORS.GOLD, bold = true}})
- announce({{text = COMMAND_PREFIX .. " help", color = COLORS.AQUA}, {text = " - Shows this help message.", color = COLORS.GRAY}})
- announce({{text = COMMAND_PREFIX .. " status", color = COLORS.AQUA}, {text = " - Shows current TPS (Redstone Signal) and bot status.", color = COLORS.GRAY}})
- announce({{text = COMMAND_PREFIX .. " mute", color = COLORS.AQUA}, {text = " - Toggles TPS alert announcements.", color = COLORS.GRAY}})
- announce({{text = COMMAND_PREFIX .. " setalerts <rs1> [rs2...]", color = COLORS.AQUA}, {text = " - Sets Redstone Signal alert thresholds (0-15).", color = COLORS.GRAY}})
- announce({{text = COMMAND_PREFIX .. " startreport", color = COLORS.AQUA}, {text = " - Starts reporting Redstone Signal every " ..TPS_REPORT_INTERVAL_SECONDS.. "s.", color = COLORS.GRAY}})
- announce({{text = COMMAND_PREFIX .. " stopreport", color = COLORS.AQUA}, {text = " - Stops reporting Redstone Signal.", color = COLORS.GRAY}})
- end
- commandHandlers.status = function(username, _)
- logDebug("Executing command: status, User: " .. username)
- local currentRSTps = getCurrentRedstoneTps()
- if currentRSTps ~= nil then
- -- Optional: Convert to display actual TPS as well
- -- local estimatedActualTps = currentRSTps * (20/15)
- -- local displayTPS = string.format("%.1f TPS (Redstone Signal: %d)", estimatedActualTps, currentRSTps)
- local displayTPS = string.format("%d", currentRSTps) -- Default: just show RS value
- announce({{text="Current Health Indicator (Redstone Signal): ", color=COLORS.GREEN}, {text=displayTPS, color=COLORS.AQUA}})
- else
- announce({{text="Could not retrieve current health indicator (Redstone Signal).", color=COLORS.RED}})
- end
- announce({{text="Alerts Muted: ", color=COLORS.GRAY}, {text=tostring(isMuted), color=(isMuted and COLORS.YELLOW or COLORS.GREEN)}})
- local thresholdsStr = ""
- for i, t in ipairs(currentTpsAlertThresholdsRS) do thresholdsStr = thresholdsStr .. t .. (i < #currentTpsAlertThresholdsRS and ", " or "") end
- announce({{text="Current Alert Thresholds (Redstone Signal <=): ", color=COLORS.GRAY}, {text=thresholdsStr, color=COLORS.YELLOW}})
- announce({{text="Reporting Redstone Signal: ", color=COLORS.GRAY}, {text=tostring(isReportingTps), color=(isReportingTps and COLORS.GREEN or COLORS.YELLOW)}})
- end
- commandHandlers.mute = function(username, _)
- logDebug("Executing command: mute, User: " .. username)
- isMuted = not isMuted
- if isMuted then
- announce({{text="Health alerts are now ", color=COLORS.YELLOW}, {text="MUTED", bold=true, color=COLORS.RED}, {text=" by " .. username .. ".", color=COLORS.YELLOW}})
- else
- announce({{text="Health alerts are now ", color=COLORS.GREEN}, {text="UNMUTED", bold=true, color=COLORS.AQUA}, {text=" by " .. username .. ".", color=COLORS.GREEN}})
- lastNotifiedTpsLevelRS = 16
- checkTpsAndAlert()
- end
- logDebug("Mute status changed to: " .. tostring(isMuted))
- end
- commandHandlers.setalerts = function(username, args)
- logDebug("Executing command: setalerts, User: " .. username .. ", Args: " .. textutils.serialize(args))
- if #args == 0 then
- announce({{text="Usage: " .. COMMAND_PREFIX .. " setalerts <rs_value1> [rs_value2...]", color=COLORS.YELLOW}})
- announce({{text="Values are Redstone Signal strength (0-15). Example: " .. COMMAND_PREFIX .. " setalerts 3 2 1", color=COLORS.YELLOW}})
- return
- end
- local newThresholdsRS = {}
- for _, arg_val in ipairs(args) do
- local num = tonumber(arg_val)
- if num and num >= 0 and num <= 15 then -- Redstone signal is 0-15
- table.insert(newThresholdsRS, math.floor(num))
- else
- announce({{text="Invalid threshold value: '", color=COLORS.RED}, {text=tostring(arg_val), color=COLORS.YELLOW}, {text="'. Must be a number between 0 and 15.", color=COLORS.RED}})
- return
- end
- end
- if #newThresholdsRS == 0 then
- announce({{text="No valid thresholds provided.", color=COLORS.RED}})
- return
- end
- table.sort(newThresholdsRS, function(a,b) return a > b end)
- currentTpsAlertThresholdsRS = newThresholdsRS
- lastNotifiedTpsLevelRS = 16
- local thresholdsStr = ""
- for i, t in ipairs(currentTpsAlertThresholdsRS) do thresholdsStr = thresholdsStr .. t .. (i < #currentTpsAlertThresholdsRS and ", " or "") end
- announce({{text="Redstone Signal alert thresholds set to: ", color=COLORS.GREEN}, {text=thresholdsStr, color=COLORS.AQUA}, {text=" by " .. username .. ".", color=COLORS.GREEN}})
- logDebug("Alert thresholds updated: " .. thresholdsStr)
- checkTpsAndAlert()
- end
- commandHandlers.startreport = function(username, _) -- Renamed from startpower
- logDebug("Executing command: startreport, User: " .. username)
- if isReportingTps then
- announce({{text="Redstone Signal reporting is already active.", color=COLORS.YELLOW}})
- return
- end
- isReportingTps = true
- announce({{text="Starting Redstone Signal reporting every "..TPS_REPORT_INTERVAL_SECONDS.."s (requested by " .. username .. ").", color=COLORS.GREEN}})
- startTpsReportTimer()
- end
- commandHandlers.stopreport = function(username, _) -- Renamed from stoppower
- logDebug("Executing command: stopreport, User: " .. username)
- if not isReportingTps then
- announce({{text="Redstone Signal reporting is not currently active.", color=COLORS.YELLOW}})
- return
- end
- isReportingTps = false
- if tpsReportTimerId then
- os.cancelTimer(tpsReportTimerId)
- tpsReportTimerId = nil
- logDebug("TPS report timer cancelled.")
- end
- announce({{text="Stopped Redstone Signal reporting (requested by " .. username .. ").", color=COLORS.GREEN}})
- end
- --#endregion
- --#region Main Loop
- local function run()
- term.clear(); term.setCursorPos(1,1)
- if DEBUG_MODE then
- local file, err = fs.open(DEBUG_LOG_FILE, "w")
- if file then
- file.write(string.format("[%s] LagBot Script Initializing - DEBUG MODE ENABLED (v1.1)\n", os.date("%Y-%m-%d %H:%M:%S")))
- file.write("======================================================================\n")
- file.close()
- else print("DEBUG LOG ERROR: Could not clear/initialize " .. DEBUG_LOG_FILE .. ": " .. (err or "unknown error")) end
- end
- logDebug("Script run() started.")
- if not chatBox then
- logDebug("WARNING: Chat Box peripheral ('" .. CHAT_BOX_PERIPHERAL_NAME .. "') not found!")
- print("WARNING: Chat Box ('" .. CHAT_BOX_PERIPHERAL_NAME .. "') not found! Chat features disabled/printed to console.")
- end
- -- Validate TPS_INPUT_SIDE
- local validSides = redstone.getSides()
- local sideIsValid = false
- for _, side in ipairs(validSides) do if side == TPS_INPUT_SIDE then sideIsValid = true; break; end end
- if not sideIsValid then
- logDebug("CRITICAL: Invalid TPS_INPUT_SIDE ('" .. TPS_INPUT_SIDE .. "') configured! Core functionality disabled.")
- print("CRITICAL: Invalid TPS_INPUT_SIDE ('" .. TPS_INPUT_SIDE .. "')! Check config. LagBot functionality disabled.")
- if chatBox then announce({{text=CHAT_BOT_NAME .. " starting with CRITICAL ERROR: Invalid TPS_INPUT_SIDE!", color=COLORS.RED, bold=true}}) end
- return -- Stop script if side is invalid, as it can't function
- end
- logDebug("TPS_INPUT_SIDE ('" .. TPS_INPUT_SIDE .. "') is valid.")
- logDebug("Announcing online status.")
- print(CHAT_BOT_NAME .. " script started. Type '" .. COMMAND_PREFIX .. " help' in chat or '@all'.")
- if chatBox then
- announce({{text = CHAT_BOT_NAME .. " online.", color = COLORS.GREEN, bold = true}, {text = " Monitoring server health via Redstone Signal on side: " .. TPS_INPUT_SIDE, color = COLORS.GRAY}})
- announce({{text = "Type '", color = COLORS.GRAY}, {text = COMMAND_PREFIX .. " help", color = COLORS.AQUA}, {text = "' or '@all' for commands.", color = COLORS.GRAY}})
- end
- checkTpsAndAlert()
- startTpsCheckTimer()
- while true do
- local eventData = {os.pullEvent()}
- local eventType = eventData[1]
- -- Optimized: If it's a redstone event and it's for our TPS_INPUT_SIDE, check TPS immediately.
- if eventType == "redstone" then
- logDebug("Redstone event received.")
- -- The redstone event itself doesn't give the side or new value in CC:Tweaked by default.
- -- We just use it as a trigger to re-check.
- checkTpsAndAlert()
- end
- logDebug("Event pulled: " .. eventType .. " - Data: " .. textutils.serialize(eventData, {compact = true, max_depth = 2}))
- if eventType == "chat" then
- local eUsername, eMessage, _, eIsHidden = eventData[2], eventData[3], eventData[4], eventData[5]
- if not eIsHidden and eMessage then
- if string.lower(eMessage) == "@all" then
- logDebug("@all command received from " .. eUsername)
- announce({{text = "Use '", color = COLORS.GREEN}, {text = COMMAND_PREFIX .. " help", color = COLORS.AQUA}, {text = "' to see what this health monitor can do.", color = COLORS.GREEN}})
- elseif string.sub(eMessage, 1, #COMMAND_PREFIX) == COMMAND_PREFIX then
- logDebug("Chat command received from " .. eUsername .. ": " .. eMessage)
- local parts = {}; for part in string.gmatch(eMessage, "[^%s]+") do table.insert(parts, part) end
- local commandName = ""; if parts[2] then commandName = string.lower(parts[2]) end
- local cmdArgs = {}; for i = 3, #parts do table.insert(cmdArgs, parts[i]) end
- logDebug("Parsed command: '" .. commandName .. "', Args: " .. textutils.serialize(cmdArgs))
- if commandHandlers[commandName] then
- local success, err = pcall(commandHandlers[commandName], eUsername, cmdArgs)
- if not success then
- logDebug("Error executing command '" .. commandName .. "': " .. tostring(err))
- announce({{text = "Command execution failed: ", color = COLORS.RED}, {text = commandName, color = COLORS.YELLOW}})
- end
- elseif commandName ~= "" then
- announce({{text = "Unknown command: '", color = COLORS.RED}, {text = commandName, color = COLORS.YELLOW},
- {text = "'. Consult '", color = COLORS.RED}, {text = COMMAND_PREFIX .. " help", color = COLORS.AQUA}, {text = "'.", color = COLORS.RED}})
- end
- end
- end
- elseif eventType == "timer" then
- local timerId = eventData[2]
- logDebug("Timer event received for ID: " .. timerId)
- if timerId == tpsCheckTimerId then
- logDebug("TPS check timer triggered.")
- checkTpsAndAlert(); startTpsCheckTimer()
- elseif timerId == tpsReportTimerId then
- logDebug("TPS report timer triggered.")
- reportCurrentTps()
- else
- logDebug("Unknown timer ID: " .. timerId)
- end
- elseif eventType == "terminate" then
- logDebug("Terminate event received. System halt.")
- if tpsCheckTimerId then os.cancelTimer(tpsCheckTimerId); logDebug("TPS check timer cancelled.") end
- if tpsReportTimerId then os.cancelTimer(tpsReportTimerId); logDebug("TPS report timer cancelled.") end
- if chatBox then announce({{text = CHAT_BOT_NAME .. " is going offline.", color = COLORS.YELLOW, bold = true}}) end
- logDebug("Script terminated."); print(CHAT_BOT_NAME .. " terminated.")
- return
- end
- end
- end
- run()
- --#endregion
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement