-- ============================================================================-- Online Player Viewer-- Requires: monitor + playerDetector peripherals-- Dependencies: CC:Tweaked + Advanced Peripherals-- ============================================================================-- ============================================================================-- Configuration-- ============================================================================local REFRESH_INTERVAL = 1 -- Refresh interval in secondslocal TEXT_SCALE = 0.5 -- Text scale (0.5 = smallest / most room)-- ============================================================================-- Color Theme-- ============================================================================local CLR = { bg = colors.black, title = colors.yellow, header = colors.cyan, text = colors.white, white = colors.white, dim = colors.lightGray, online = colors.lime, offline = colors.red, warn = colors.orange, highlight = colors.green, accent = colors.pink, border = colors.gray,}-- Dimension short nameslocal DIM_NAMES = { ["minecraft:overworld"] = "Overworld", ["minecraft:the_nether"] = "Nether", ["minecraft:the_end"] = "End",}-- ============================================================================-- Utility Functions-- ============================================================================--- Format dimension namelocal function formatDimension(dim) return DIM_NAMES[dim] or dim or "???"end--- Format coordinateslocal function formatPos(x, y, z) if not x then return "---, ---, ---" end return string.format("%d, %d, %d", math.floor(x), math.floor(y), math.floor(z))end--- Format healthlocal function formatHealth(health, maxHealth) if not health then return "?/?" end return string.format("%.0f/%.0f", health, maxHealth)end--- Draw centered textlocal function drawCentered(mon, y, text, color) local w = mon.getSize() mon.setTextColor(color or CLR.text) local x = math.floor((w - #text) / 2) + 1 if x < 1 then x = 1 end mon.setCursorPos(x, y) mon.write(text)end--- Draw horizontal separatorlocal function drawSeparator(mon, y, char, color) local w = mon.getSize() char = char or "-" mon.setTextColor(color or CLR.border) mon.setCursorPos(1, y) mon.write(string.rep(char, w))end--- Truncate text to fit widthlocal function truncate(text, maxLen) if #text <= maxLen then return text end return text:sub(1, maxLen - 1) .. "."end-- ============================================================================-- Peripheral Setup-- ============================================================================local function findPeripherals() local monitor = nil local detector = nil local names = peripheral.getNames() for _, name in ipairs(names) do local ptype = peripheral.getType(name) if ptype == "monitor" and not monitor then monitor = peripheral.wrap(name) elseif ptype == "player_detector" and not detector then detector = peripheral.wrap(name) end end return monitor, detectorend-- ============================================================================-- Data Fetching-- ============================================================================local function getPlayerData(detector) local players = {} local ok, onlineList = pcall(detector.getOnlinePlayers, detector) if not ok then return nil, "Failed to get player list: " .. tostring(onlineList) end if not onlineList or #onlineList == 0 then return {}, nil end for _, name in ipairs(onlineList) do local info = detector.getPlayer(name) if info then table.insert(players, { name = name, x = info.x, y = info.y, z = info.z, dimension = info.dimension, health = info.health, maxHealth = info.maxHealth, }) else -- Still show player even if detail fetch fails table.insert(players, { name = name, x = nil, y = nil, z = nil, dimension = nil, health = nil, maxHealth = nil, }) end end -- Sort alphabetically table.sort(players, function(a, b) return a.name:lower() < b.name:lower() end) return players, nilend-- ============================================================================-- Rendering-- ============================================================================-- Fixed row layout (the static frame is drawn once; only dynamic rows update)local ROW_STATS = 4 -- player count / refresh rate / timestamplocal ROW_HEADER = 6 -- column headerslocal ROW_FIRST = 8 -- first player row--- Blank a vertical region with the background color. Used instead of--- clearing the whole monitor so a refresh never flashes the entire screen.local function clearRows(mon, y1, y2) local w = mon.getSize() for y = y1, y2 do mon.setBackgroundColor(CLR.bg) mon.setCursorPos(1, y) mon.write(string.rep(" ", w)) endend--- Draw the static frame (title bar, separators, column header) once.--- It stays on screen; only the dynamic rows are rewritten afterwards.local function drawStaticFrame(mon) local w = mon.getSize() mon.setBackgroundColor(CLR.bg) mon.clear() -- Title bar mon.setTextColor(CLR.title) drawSeparator(mon, 1, "=", CLR.title) drawCentered(mon, 2, "** Online Player Viewer **", CLR.title) drawSeparator(mon, 3, "=", CLR.title) -- Column header local nameW = math.min(18, math.floor(w * 0.22)) local dimW = math.min(10, math.floor(w * 0.12)) local posW = math.min(24, math.floor(w * 0.35)) local hpW = math.min(10, math.floor(w * 0.12)) mon.setTextColor(CLR.header) mon.setCursorPos(2, ROW_HEADER) local headerFmt = "%-" .. nameW .. "s %-" .. dimW .. "s %-" .. posW .. "s %" .. hpW .. "s" mon.write(string.format(headerFmt, "Name", "Dimension", "Position (X, Y, Z)", "Health")) drawSeparator(mon, ROW_HEADER + 1, "-", CLR.border)end--- Redraw only the stats row (no full clear, so no flicker)local function renderStats(mon, playerCount, timestamp) local w = mon.getSize() mon.setBackgroundColor(CLR.bg) mon.setCursorPos(1, ROW_STATS) mon.write(string.rep(" ", w)) mon.setTextColor(CLR.text) mon.setCursorPos(2, ROW_STATS) mon.write("Online: ") mon.setTextColor(CLR.online) mon.write(tostring(playerCount)) mon.setTextColor(CLR.dim) mon.setCursorPos(math.floor(w / 2) + 1, ROW_STATS) mon.write("Refresh: " .. REFRESH_INTERVAL .. "s") -- Timestamp on the right local timeStr = "Updated: " .. timestamp mon.setCursorPos(w - #timeStr, ROW_STATS) mon.write(timeStr)endlocal function renderPlayerTable(mon, players) local w, h = mon.getSize() local lastRow = h - 2 if #players == 0 then clearRows(mon, ROW_FIRST, lastRow) drawCentered(mon, ROW_FIRST, "(No players online)", CLR.dim) return end -- Column widths local nameW = math.min(18, math.floor(w * 0.22)) local dimW = math.min(10, math.floor(w * 0.12)) local posW = math.min(24, math.floor(w * 0.35)) local hpW = math.min(10, math.floor(w * 0.12)) local row = ROW_FIRST for i, p in ipairs(players) do if row > lastRow then mon.setTextColor(CLR.dim) mon.setCursorPos(2, lastRow) mon.write("... +" .. (#players - i + 1) .. " more players") break end -- Alternating row colors if i % 2 == 0 then mon.setTextColor(CLR.text) else mon.setTextColor(CLR.white) end mon.setCursorPos(2, row) -- Name local name = truncate(p.name, nameW) mon.write(string.format("%-" .. nameW .. "s", name)) mon.setCursorPos(2 + nameW + 2, row) -- Dimension local dim = truncate(formatDimension(p.dimension), dimW) if p.dimension == "minecraft:the_nether" then mon.setTextColor(CLR.warn) elseif p.dimension == "minecraft:the_end" then mon.setTextColor(CLR.accent) end mon.write(string.format("%-" .. dimW .. "s", dim)) -- Position if i % 2 == 0 then mon.setTextColor(CLR.text) else mon.setTextColor(CLR.white) end mon.setCursorPos(2 + nameW + 2 + dimW + 2, row) local pos = formatPos(p.x, p.y, p.z) mon.write(string.format("%-" .. posW .. "s", pos)) -- Health mon.setCursorPos(2 + nameW + 2 + dimW + 2 + posW + 2, row) local hp = formatHealth(p.health, p.maxHealth) if p.health and p.maxHealth then local ratio = p.health / p.maxHealth if ratio < 0.25 then mon.setTextColor(CLR.offline) elseif ratio < 0.5 then mon.setTextColor(CLR.warn) else mon.setTextColor(CLR.highlight) end end mon.write(string.format("%" .. hpW .. "s", hp)) row = row + 1 end -- Blank the rows left over when the new list is shorter clearRows(mon, row, lastRow)endlocal function renderFooter(mon, errorMsg) local w, h = mon.getSize() mon.setBackgroundColor(CLR.bg) mon.setCursorPos(1, h - 1) mon.write(string.rep(" ", w)) mon.setCursorPos(1, h) mon.write(string.rep(" ", w)) if errorMsg then drawSeparator(mon, h - 1, "-", CLR.warn) mon.setTextColor(CLR.warn) mon.setCursorPos(2, h) mon.write("! " .. truncate(errorMsg, w - 4)) else drawSeparator(mon, h - 1, "-", CLR.border) mon.setTextColor(CLR.dim) mon.setCursorPos(2, h) mon.write("CC:Tweaked + Advanced Peripherals") endend-- ============================================================================-- Main Loop-- ============================================================================local function main() local monitor, detector = findPeripherals() if not monitor then print("Error: Monitor peripheral not found!") print("Available: " .. table.concat(peripheral.getNames(), ", ")) return end if not detector then print("Error: Player detector peripheral not found!") print("Available: " .. table.concat(peripheral.getNames(), ", ")) -- Show error on monitor too monitor.setTextScale(TEXT_SCALE) monitor.setBackgroundColor(CLR.bg) monitor.clear() monitor.setTextColor(CLR.warn) drawCentered(monitor, math.floor(monitor.getSize() / 2), "! Player detector not found !", CLR.warn) return end -- Init monitor monitor.setTextScale(TEXT_SCALE) print("Online Player Viewer started!") print("Monitor size: " .. monitor.getSize()) print("Press Ctrl+T to stop") -- Draw the static frame once; only dynamic rows are redrawn each tick drawStaticFrame(monitor) -- Main loop while true do local timestamp = os.date("%H:%M:%S") local players, err = getPlayerData(detector) renderStats(monitor, players and #players or 0, timestamp) if players then renderPlayerTable(monitor, players) else local _, h = monitor.getSize() clearRows(monitor, ROW_FIRST, h - 2) end renderFooter(monitor, err) sleep(REFRESH_INTERVAL) endend-- ============================================================================-- Entry Point-- ============================================================================local ok, err = pcall(main)if not ok then local monitor = peripheral.find("monitor") if monitor then monitor.setTextScale(TEXT_SCALE) monitor.setBackgroundColor(colors.black) monitor.clear() monitor.setTextColor(colors.red) monitor.setCursorPos(1, 1) monitor.write("Crash:\n" .. tostring(err)) end print("Error: " .. tostring(err))end