diff --git a/ticketmachine.lua b/ticketmachine.lua index 6a48f61..b30615f 100644 --- a/ticketmachine.lua +++ b/ticketmachine.lua @@ -1,8 +1,12 @@ local CURRENT_STATION_CODE = 'Ticket-Machine' local API_BASE = 'https://ticket.fse-media.group/api' -local VERSION = 'v1.5.25' +local VERSION = 'v1.5.26' local UPDATE_CHECK_INTERVAL = 5 +local pack = table.pack or function(...) + return { n = select('#', ...), ... } +end + -- ########################### -- Core HTTP & JSON Utilities -- ########################### @@ -340,13 +344,172 @@ local function reportIssue(errMsg, detail) postJSONResult(url, payload) end +local GPU_CHAR_WIDTH = 4 +local GPU_CHAR_HEIGHT = 6 + +local GPU_COLOR_MAP = { + [colors.white] = 0xFFFFFFFF, + [colors.orange] = 0xFFFFA500, + [colors.magenta] = 0xFFFF00FF, + [colors.lightBlue] = 0xFF66CCFF, + [colors.yellow] = 0xFFFFFF00, + [colors.lime] = 0xFF00FF00, + [colors.pink] = 0xFFFFB6C1, + [colors.gray] = 0xFF808080, + [colors.lightGray] = 0xFFD3D3D3, + [colors.cyan] = 0xFF00FFFF, + [colors.purple] = 0xFF800080, + [colors.blue] = 0xFF0000FF, + [colors.brown] = 0xFF8B4513, + [colors.green] = 0xFF008000, + [colors.red] = 0xFFFF0000, + [colors.black] = 0xFF000000, +} + +local function ccColorToARGB(col) + return GPU_COLOR_MAP[col] or GPU_COLOR_MAP[colors.white] +end + +local function isTmGpu(dev) + return type(dev) == 'table' + and type(dev.getSize) == 'function' + and type(dev.drawText) == 'function' + and type(dev.fill) == 'function' + and type(dev.sync) == 'function' +end + +local function buildTmGpuTerm(dev) + local gpuTerm = { + kind = 'tm_gpu', + device = dev, + cursorX = 1, + cursorY = 1, + textColor = colors.white, + backgroundColor = colors.black, + width = 1, + height = 1, + pixelWidth = 0, + pixelHeight = 0, + resolutionConfigured = false, + } + + local function syncSize() + if type(gpuTerm.device.refreshSize) == 'function' then + pcall(gpuTerm.device.refreshSize) + end + + for _ = 1, 3 do + local size = pack(pcall(gpuTerm.device.getSize)) + local px = tonumber(size[2]) or 0 + local py = tonumber(size[3]) or 0 + if size[1] and px > 0 and py > 0 then + if not gpuTerm.resolutionConfigured and type(gpuTerm.device.setSize) == 'function' then + gpuTerm.resolutionConfigured = true + local resolution = tonumber(size[6]) or 0 + if resolution > 0 and resolution < 32 then + pcall(gpuTerm.device.setSize, 32) + os.sleep(0) + size = pack(pcall(gpuTerm.device.getSize)) + px = tonumber(size[2]) or px + py = tonumber(size[3]) or py + end + end + gpuTerm.pixelWidth = px + gpuTerm.pixelHeight = py + gpuTerm.width = math.max(1, math.floor(px / GPU_CHAR_WIDTH)) + gpuTerm.height = math.max(1, math.floor(py / GPU_CHAR_HEIGHT)) + return gpuTerm.width, gpuTerm.height + end + os.sleep(0) + end + + if gpuTerm.pixelWidth <= 0 then gpuTerm.pixelWidth = 64 end + if gpuTerm.pixelHeight <= 0 then gpuTerm.pixelHeight = 32 end + gpuTerm.width = math.max(1, math.floor(gpuTerm.pixelWidth / GPU_CHAR_WIDTH)) + gpuTerm.height = math.max(1, math.floor(gpuTerm.pixelHeight / GPU_CHAR_HEIGHT)) + return gpuTerm.width, gpuTerm.height + end + + function gpuTerm.getSize() + return syncSize() + end + + function gpuTerm.setCursorPos(x, y) + gpuTerm.cursorX = math.max(1, math.floor(tonumber(x) or 1)) + gpuTerm.cursorY = math.max(1, math.floor(tonumber(y) or 1)) + end + + function gpuTerm.setTextColor(col) + gpuTerm.textColor = GPU_COLOR_MAP[col] and col or colors.white + end + + function gpuTerm.setBackgroundColor(col) + gpuTerm.backgroundColor = GPU_COLOR_MAP[col] and col or colors.black + end + + function gpuTerm.clear() + syncSize() + pcall(gpuTerm.device.fill, ccColorToARGB(gpuTerm.backgroundColor)) + gpuTerm.cursorX = 1 + gpuTerm.cursorY = 1 + pcall(gpuTerm.device.sync) + end + + function gpuTerm.write(text) + text = tostring(text or '') + if #text == 0 then return end + + local width, height = syncSize() + local originalLen = #text + local x = gpuTerm.cursorX + local y = gpuTerm.cursorY + + if y < 1 or y > height or x > width then + gpuTerm.cursorX = x + originalLen + return + end + + if x < 1 then + local shift = 1 - x + if shift >= #text then + gpuTerm.cursorX = x + originalLen + return + end + text = text:sub(shift + 1) + x = 1 + end + + local visible = text:sub(1, math.max(0, width - x + 1)) + if #visible > 0 then + local px = ((x - 1) * GPU_CHAR_WIDTH) + 1 + local py = ((y - 1) * GPU_CHAR_HEIGHT) + 1 + local bg = ccColorToARGB(gpuTerm.backgroundColor) + local fg = ccColorToARGB(gpuTerm.textColor) + pcall(gpuTerm.device.filledRectangle, px, py, #visible * GPU_CHAR_WIDTH, GPU_CHAR_HEIGHT, bg) + pcall(gpuTerm.device.drawText, px, py, visible, fg, bg, 1, 0) + pcall(gpuTerm.device.sync) + end + + gpuTerm.cursorX = gpuTerm.cursorX + originalLen + end + + function gpuTerm.sync() + pcall(gpuTerm.device.sync) + end + + return gpuTerm +end + -- ########################### -- Peripheral discovery -- ########################### local SIDE_PRIORITY = { top = 1, bottom = 2, left = 3, right = 4, front = 5, back = 6 } local REDSTONE_SIDES = { 'right', 'left', 'top', 'bottom', 'front', 'back' } +local nativeTerm = (term and type(term.current) == 'function' and term.current()) or term local monitor = nil local monitorName = nil +local tmGpu = nil +local tmGpuName = nil local ticketVendingMachine = nil local ticketVendingMachineName = nil local speaker = nil @@ -403,23 +566,30 @@ local function findPeripheralByType(typeName) return nil, nil end -local termDev = term +local termDev = nativeTerm or term local w, h = termDev.getSize() local function refreshDevices() local prevSignature = table.concat({ tostring(monitorName or ''), + tostring(tmGpuName or ''), tostring(ticketVendingMachineName or ''), tostring(speakerName or '') }, '|') monitor, monitorName = findPeripheralByType('monitor') + tmGpu, tmGpuName = findPeripheralByType('tm_gpu') ticketVendingMachine, ticketVendingMachineName = findPeripheralByType('ticket_vending_machine') speaker, speakerName = findPeripheralByType('speaker') - termDev = monitor or term - if monitor then pcall(monitor.setTextScale, 0.5) end + if tmGpu and isTmGpu(tmGpu) then + termDev = buildTmGpuTerm(tmGpu) + else + termDev = monitor or nativeTerm or term + if monitor then pcall(monitor.setTextScale, 0.5) end + end w, h = termDev.getSize() local nextSignature = table.concat({ tostring(monitorName or ''), + tostring(tmGpuName or ''), tostring(ticketVendingMachineName or ''), tostring(speakerName or '') }, '|') @@ -430,6 +600,30 @@ end refreshDevices() +local nativePullEvent = os.pullEvent +local function pullEventCompat(filter) + while true do + local ev = pack(nativePullEvent()) + local eventName = ev[1] + + if eventName == 'tm_monitor_touch' then + local px = tonumber(ev[3]) or 1 + local py = tonumber(ev[4]) or 1 + ev[1] = 'monitor_touch' + ev[2] = tostring(ev[2] or tmGpuName or '') + ev[3] = math.max(1, math.floor((px - 1) / GPU_CHAR_WIDTH) + 1) + ev[4] = math.max(1, math.floor((py - 1) / GPU_CHAR_HEIGHT) + 1) + eventName = ev[1] + end + + if not filter or filter == eventName then + return table.unpack(ev, 1, ev.n or #ev) + end + end +end + +os.pullEvent = pullEventCompat + local function saveCardIssueSnapshot(cardData) pcall(function() ensureDir('logs/last_card_issue.json')