Modular Computers the manual GitHub

Operator Card

An operator card runs server commands, as a command block does. It exists in creative mode only.

Facts

| | | |—|—| | Type | operator | | Address | An expansion slot path such as exp:0, or a hub’s | | Availability | Creative only | | Interrupt | None | | Info | kind (operator) |

The card runs a command at its own block (in a case, or in an expansion hub, at the hub), with a command block’s permission: time, weather, give, title and the like (a game master’s), but not op or stop. Its commands come from “Operator Card”, and each command is logged on the server, so its admins see what ran. It returns how the command went and what it said. What a command says goes back to the program, not to any player.

In mcos

/dev/operator0 (operator1 for the next card) runs commands.

echo time set day > /dev/operator0
cat /dev/operator0

The second command prints what the server said, such as Set the time to 1000.

Methods

command(text) → {success, result, output}

Runs a server command, with or without its leading /, and answers how it went. success says whether it worked, result is the command’s value (0 when it failed), and output is what it said (its lines, each ending in a new line). A command that fails still answers (with success false); only an empty command fails the call, with no command. A card not in a world answers success = false, output = "not in a world".

get() → string

What the last command said (its output), or "" before the first.

set({command = text}) → {success, result, output}

Runs a command, as command does: what a line written to the file does. A command that fails makes this call fail, with what it said (it failed when it said nothing). A table without a command string fails with no command.

Events

None.

Examples

Endless day: at dusk the program writes two commands to the card’s file:

-- Endless day: at dusk, set the world's time back to morning and clear the weather.
-- lua operator-daylight.lua &
--
-- The operator card's file runs a line written to it as a server command, as a command block
-- would, at once. A command that fails fails the write, and reading the file then says what the
-- server answered. The world's clock is the computer's (os.date's hours are the game's), so the
-- program looks once a minute of game time and sleeps in between.
local sys = require("sys")

local DUSK = 18          -- the hour (of the game's 24) after which it is time to act
local CHECK = 50         -- seconds between looks (a game hour is 50 seconds)
local COMMANDS = {"time set day", "weather clear"}

local card = assert(io.open("/dev/operator0", "w"))

while true do
  if os.date("*t").hour >= DUSK then
    for _, command in ipairs(COMMANDS) do
      card:write(command, "\n")
      local ok = card:flush()
      if not ok then
        local answer = io.open("/dev/operator0")
        io.stderr:write("daylight: ", command, ": ", answer:read("a"))
        answer:close()
      end
    end
  end
  sys.sleep(CHECK)
end

A file of commands run through the command ioctl, each logged, stopping at the first that fails:

-- Run a file of server commands, a line each, and log how each went:
-- lua operator-script.lua setup.txt [log]
--
-- Blank lines and lines starting with # are skipped. Each command goes to the operator card's
-- command method (an ioctl on its file, /dev/operator0), which answers a table: success, result
-- (the command's value) and output (what the server said). The log gets a line per command; the
-- first command that fails stops the run, and the exit status is 1, so a shell script can tell.
local sys = require("sys")

local LOG = "/home/operator.log"  -- where the log goes, unless named

local script, logpath = arg[1], arg[2] or LOG
if not script then
  io.stderr:write("usage: operator-script.lua FILE [LOG]\n")
  os.exit(2)
end

local input = assert(io.open(script))
local log = assert(io.open(logpath, "a"))
local card = assert(sys.open("/dev/operator0"))

local status = 0
local number = 0
for line in input:lines() do
  number = number + 1
  local command = line:match("^%s*(.-)%s*$")
  if command ~= "" and command:sub(1, 1) ~= "#" then
    local answer, why = sys.ioctl(card, "command", command)
    if not answer then
      log:write(("%d: %s: error: %s\n"):format(number, command, why))
      status = 1
    else
      local said = answer.output:gsub("\n", " "):gsub("%s+$", "")
      log:write(("%d: %s: %s %d %s\n"):format(number, command, answer.success and "ok" or "FAILED", answer.result, said))
      if not answer.success then
        status = 1
      end
    end
    if status ~= 0 then
      io.stderr:write("operator-script: stopped at line ", number, "\n")
      break
    end
  end
end

sys.close(card)
input:close()
log:close()
os.exit(status)

A greeter at the door: a redstone card hears a pressure plate, set runs a title command, and get says who it was shown to:

-- Greet whoever steps on the pressure plate at the door, and keep a visitors' log.
-- lua operator-greeter.lua &
--
-- The plate's signal comes in on a redstone card (/dev/redstone0, a line a face: "face level
-- powered"); sys.poll waits for its change (PRI). On each step the program runs a server command
-- through the operator card's set method (as a line written to its file would: a command that
-- fails fails the call, with what the server said), then its get method gives what the command
-- said (who was shown the greeting), which goes in the log with the world's date and time.
local sys = require("sys")

local PLATE = "south"                  -- the face the plate's signal comes in on
-- The command run for each visitor: a title on the screens of the players within 5 blocks of the
-- card (a command runs at its block). The server answers "Showing new title for <player>".
local GREETING = 'title @a[distance=..5] title "Welcome home!"'
local LOG = "/home/visitors.log"

local redstone = assert(io.open("/dev/redstone0"))
local card = assert(sys.open("/dev/operator0"))
local watch = {[sys.fileno(redstone)] = {PRI = true}}

-- Whether the plate is pressed now: the file read again from its start.
local function pressed()
  redstone:seek("set", 0)
  return (tonumber(redstone:read("a"):match(PLATE .. " (%d+)")) or 0) > 0
end

local was = pressed()
while true do
  sys.poll(watch)  -- block until the redstone card changes
  local now = pressed()
  if now and not was then
    local done, why = sys.ioctl(card, "set", {command = GREETING})
    local log = assert(io.open(LOG, "a"))
    if done then
      log:write(os.date("%c"), " visitor: ", (sys.ioctl(card, "get"):gsub("\n$", "")), "\n")
    else
      log:write(os.date("%c"), " greeting failed: ", tostring(why), "\n")
    end
    log:close()
  end
  was = now
end