Created
March 29, 2015 15:12
-
-
Save boq/67300900797b113a124d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- OpenPeripheral terminal glasses demo | |
-- by boq | |
local p = peripheral.wrap("back") | |
if p == nil then | |
print("Peripheral not found") | |
return | |
end | |
p.clear() | |
-- helper function and constants | |
function pack(...) | |
return {...} | |
end | |
FORMAT_CHAR = "\194\167" | |
FORMAT_BOLD = FORMAT_CHAR .. 'l' | |
-- NOTE | |
-- this program works with multiple users (i.e. multiple players attached to same terminal) so it may be more complicated then single user variant | |
-- if it's not needed, programs may as well operate only on global surface | |
local players = {} | |
local attachedCount = 0 | |
local capturedCount = 0 | |
-- initialize global display surface | |
local labelPlayers = p.addText(-1, 0, "Player count: 0/0"); | |
labelPlayers.setScreenAnchor("right", "top") -- screen origin (0,0) for this object will be in top right corner | |
labelPlayers.setObjectAnchor("right", "bottom") -- "center" of object will be on bottom right corner | |
labelPlayers.setRotation(-90) -- object will be rotated by 90 degrees counter-clockwise around object anchor (bottom right corner) | |
local function initializePlayer(name, uuid) | |
local playerData = { name = name, uuid = uuid} | |
-- now we can use private surface, visible only to owner player | |
playerData.surface = p.getSurfaceByUUID(playerData.uuid) | |
labelPlayer = playerData.surface.addText(0, 0, "Hello " .. playerData.name) | |
labelPlayer.setAlignment("middle","top") -- identical as 'labelPlayer.setScreenAnchor("middle","top") labelPlayer.setObjectAnchor("middle","top")' | |
-- Minecraft color codes are interpreted (http://minecraft.gamepedia.com/Formatting_codes) - but since Lua does not support Unicode, you need to use "\194\167" instead of "\x00A7" | |
playerData.labelWaiting = playerData.surface.addText(0, 0, FORMAT_BOLD .. "CAPTURE MODE: OFF", 0xFF0000) | |
playerData.labelWaiting.setAlignment("right", "bottom") | |
-- add to global list | |
-- general hint: it's always safer to use UUID instead of names! | |
players[playerData.uuid] = playerData | |
end | |
local function changeCounter(deltaAttached, deltaCaptured) | |
attachedCount = attachedCount + deltaAttached | |
capturedCount = capturedCount + deltaCaptured | |
labelPlayers.setText("Player count: " .. attachedCount .. "/" .. capturedCount) | |
end | |
-- initialize already attached players | |
for _, user in pairs(p.getUsers()) do | |
initializePlayer(user.name, user.uuid) | |
changeCounter(1, 0) | |
end | |
local timerCallbacks = {} | |
p.sync() -- this operation is needed to make changes visible to player | |
-- classic event loop | |
while true do | |
evt = pack(os.pullEvent()) | |
-- full list of glasses events: https://gist.github.com/boq/9a098b89ee102cbf1b2b | |
print(evt[1]) | |
-- easy break condition - it's just ComputerCraft event | |
if evt[1] == "key" then | |
break -- exit loop | |
-- player started wearing glasses | |
elseif evt[1] == "glasses_attach" then | |
local name = evt[3] | |
local uuid = evt[4] | |
initializePlayer(name, uuid) | |
changeCounter(1, 0) | |
-- player logged out or took off glasses | |
elseif evt[1] == "glasses_detach" then | |
-- components on private surface are automatically cleared when player removes glasses | |
local playerUuid = evt[4] | |
players[playerUuid] = nil | |
changeCounter(-1,0) | |
-- player used wireless keyboard - computer will now start getting keyboard and mouse events | |
elseif evt[1] == "glasses_capture" then | |
changeCounter(0, 1) | |
local playerUuid = evt[4] | |
local playerData = players[playerUuid] | |
if playerData then | |
local surface = playerData.surface | |
playerData.labelWaiting.setText(FORMAT_BOLD .. "CAPTURE MODE: ON") | |
playerData.labelWaiting.setColor(0x00FF00) | |
-- middle boxes - will be used as bottons and to present click position | |
local function createBox(color) | |
local box = surface.addBox(0,0, 25, 25, color) | |
box.setScreenAnchor("middle","middle") | |
box.setUserdata(color) -- userdata is variable that can be used to store anything (no type or content restrictions, except for functions). It's also not synchronized to clients | |
return box | |
end | |
createBox(0xFF0000).setObjectAnchor("left", "top") | |
createBox(0x00FF00).setObjectAnchor("right", "top") | |
createBox(0x0000FF).setObjectAnchor("right", "bottom") | |
createBox(0xFFFFFF).setObjectAnchor("left", "bottom") | |
-- horizontal progress bar | |
surface.addBox(0,20, 100, 5, 0x000000).setAlignment("middle","top") | |
-- vertical progress bar | |
surface.addBox(0,0, 5, 100, 0x000000).setAlignment("right","middle") | |
playerData.horizontalMarker = surface.addBox(0, 20, 5, 5, 0xFFFFFF) | |
playerData.horizontalMarker.setAlignment("middle","top") | |
playerData.verticalMarker = surface.addBox(0, 0, 5, 5, 0xFFFFFF) | |
playerData.verticalMarker.setAlignment("right","middle") | |
-- pseudo-console stuff | |
playerData.textInput = surface.addText(0,0,">", 0x00FF00) | |
playerData.textInput.setScreenAnchor("left", "middle") | |
playerData.textInput.setObjectAnchor("left", "bottom") | |
playerData.textOutput = surface.addText(0,0,"", 0x0000FF) | |
playerData.textOutput.setScreenAnchor("left", "middle") | |
playerData.textOutput.setObjectAnchor("left", "top") | |
playerData.textOutput.setVisible(false) | |
-- mouse button indicator | |
surface.addBox(10, 10, 19, 9, 0xAAAAAA).setObjectAnchor("middle","top") -- button indicator background | |
playerData.mouseButtons = {} | |
local function createMouseButton(deltaX) | |
local button = surface.addBox(10 + deltaX, 12, 5, 5, 0xFF0000) | |
button.setZ(5) -- move it over box (default Z == 0) | |
button.setVisible(false) | |
button.setObjectAnchor("middle","top") | |
return button | |
end | |
-- mouse and keyboard codes are same as LWJGL, see http://minecraft.gamepedia.com/Key_codes | |
playerData.mouseButtons[0] = createMouseButton(-5) -- left button | |
playerData.mouseButtons[1] = createMouseButton(5) -- right button | |
playerData.mouseButtons[2] = createMouseButton(0) -- middle button | |
end | |
-- player exited from keyboard GUI | |
elseif evt[1] == "glasses_release" then | |
changeCounter(0, -1) | |
local playerUuid = evt[4] | |
local playerData = players[playerUuid] | |
if playerData then | |
-- recreate display to initial state | |
playerData.surface.clear() | |
initializePlayer(playerData.name, playerData.uuid) | |
end | |
-- player pressed mouse button anywhere in GUI | |
elseif evt[1] == "glasses_mouse_down" then | |
local playerUuid = evt[4] | |
local playerData = players[playerUuid] | |
if playerData then | |
local buttonId = evt[5] | |
-- changing visibility is faster than recreating and removing element every time | |
local button = playerData.mouseButtons[buttonId] | |
if button then | |
button.setVisible(true) | |
end | |
end | |
-- player released mouse button anywhere in GUI | |
elseif evt[1] == "glasses_mouse_up" then | |
local playerUuid = evt[4] | |
local playerData = players[playerUuid] | |
local buttonId = evt[5] | |
if playerData then | |
button = playerData.mouseButtons[buttonId] | |
if button then | |
button.setVisible(false) | |
end | |
end | |
-- player clicked on component (if you got this event, then you won't get glasses_mouse_down. Same for glasses_component_mouse_up and glasses_mouse_up) | |
elseif evt[1] == "glasses_component_mouse_down" then | |
local isPrivate = evt[6] | |
if isPrivate then | |
local playerUuid = evt[4] | |
local playerData = players[playerUuid] | |
if playerData then | |
local objectId = evt[5] | |
local component = playerData.surface.getObjectById(objectId) | |
local userdata = component.getUserdata() | |
if userdata then -- only rectangles in middle have that value filled | |
-- capture control is special object used to control few properties of keyboard GUI | |
local capture = p.getCaptureControl(playerUuid) | |
capture.setBackground(userdata) | |
playerData.horizontalMarker.setColor(userdata) | |
playerData.verticalMarker.setColor(userdata) | |
end | |
-- click position is given in pixels from top left component corner | |
local clickX = evt[7] | |
local clickY = evt[8] | |
-- -0.5, since anchor is in middle of screen | |
playerData.horizontalMarker.setX((clickX / 25.0 - 0.5) * 100) | |
playerData.verticalMarker.setY((clickY / 25.0 - 0.5) * 100) | |
end | |
end | |
elseif evt[1] == "glasses_key_down" then | |
local playerUuid = evt[4] | |
local playerData = players[playerUuid] | |
if playerData then | |
local code = evt[5] -- control code, see http://minecraft.gamepedia.com/Key_codes for keymap | |
local char = evt[6] -- may be 0 for some control characters | |
local input = playerData.textInput | |
local contents = input.getText() | |
if code == 14 then -- backspace | |
if contents:len() > 1 then | |
input.setText(contents:sub(0, -2)) | |
end | |
elseif code == 28 then -- enter | |
input.setText(">") | |
local function setOutput(text, color) | |
playerData.textOutput.setText(text) | |
playerData.textOutput.setColor(color) | |
playerData.textOutput.setVisible(true) | |
local timerId = os.startTimer(1) | |
timerCallbacks[timerId] = function () | |
playerData.textOutput.setVisible(false) | |
end | |
end | |
-- alternative to writing tons of ifs. Normally would be initialized once before main loop, but this is demo/semi-tutorial | |
local commands = {} | |
function commands.hello(playerData) | |
setOutput("Hi!", 0xFFFF00) | |
end | |
function commands.exit(playerData) | |
-- manually exit keyboard GUI. Player can do it itself by using escape button (same as normal Minecraft GUIs) | |
p.getCaptureControl(playerData.uuid).stopCapturing() | |
end | |
-- sorry, couldn't resist | |
local function createColorCommand(color) | |
return function(playerData) | |
playerData.textInput.setColor(color) | |
setOutput("OK", color) | |
end | |
end | |
commands.red = createColorCommand(0xFF0000) | |
commands.green = createColorCommand(0x00FF00) | |
commands.blue = createColorCommand(0x0000FF) | |
command = contents:sub(2, -1) | |
commandSub = commands[command] -- skip console prompt | |
if commandSub then | |
commandSub(playerData) | |
else | |
setOutput("invalid command: " .. command, 0xFF0000) | |
end | |
elseif string.byte(char) >= 0x20 then -- skip unprintable control chars | |
input.setText(contents .. char) | |
end | |
end | |
elseif evt[1] == "timer" then | |
local timerId = evt[2] | |
callback = timerCallbacks[timerId] | |
if callback then callback() end | |
timerCallbacks[timerId] = nil | |
end | |
-- sync() shouldn't be called often | |
-- if called without changes, nothing will happen | |
-- this method is synchronized - i.e. it can be executed only once per game tick (1/20 s). Other instructions don't have that limit | |
p.sync() | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
What version of OP-A are you using for this?