diff --git a/README.md b/README.md index 08f375e..db818a7 100644 --- a/README.md +++ b/README.md @@ -52,4 +52,5 @@ This comes with absolutely no guarantee of support or correct function, although * `heavbiome` - some work on biome generation with Perlin noise. * `block_scope.py` - Python uses function scoping rather than block scoping. Some dislike this. I made a decorator to switch to block scoping. * `mpris_smart_toggle.py` - playerctl play-pause sometimes does not play or pause the media I want played or paused (it seems to use some arbitrary selection order). This does it somewhat better by tracking the last thing which was playing. -* `rec_rewrite.py` - in the spirit (and blatantly copypasted code) of `block_scope.py`, rewrite recursive functions as iterative using a heap-allocated stack and generators. \ No newline at end of file +* `rec_rewrite.py` - in the spirit (and blatantly copypasted code) of `block_scope.py`, rewrite recursive functions as iterative using a heap-allocated stack and generators. +* `computercraft` - CC(:T) projects - see folder README. Consolidated from Pastebin and various internal records. \ No newline at end of file diff --git a/computercraft/3dgraph_master.lua b/computercraft/3dgraph_master.lua new file mode 100644 index 0000000..d368316 --- /dev/null +++ b/computercraft/3dgraph_master.lua @@ -0,0 +1,73 @@ + local m = peripheral.find "modem" +local CHAN = 7101 +m.open(CHAN) + +local function send(ms) m.transmit(CHAN, CHAN, ms) end + +local function receive() + while true do + local _, _, c, rc, ms = os.pullEvent "modem_message" + if type(ms) == "table" then + return ms + end + end +end + +local nodes = {} + +parallel.waitForAny(function() + send { "ping" } + while true do + local ty, id = unpack(receive()) + if ty == "pong" then + print(id, "detected") + table.insert(nodes, id) + end + end +end, function() sleep(0.5) end) + +local d_min = {202, 65, 231} +local d_max = {202 + 63, 65 + 63, 231 + 63} +local block = "botania:managlass" + +local eqn = ... +local fn, err = load(("local x, y, z = ...; return %s"):format(eqn), "=eqn", "t", math) +if not fn then error("syntax error: " .. err, 0) end + +print(#nodes, "nodes are available") + +local x_range = d_max[1] - d_min[1] + 1 +local split = math.floor(x_range / #nodes) + +local commands = {} +local x_acc = d_min[1] +for k, v in ipairs(nodes) do + local x_size = split + if k == #nodes then + x_size = x_range - ((#nodes - 1) * split) + end + local t = x_acc + x_acc = x_acc + x_size + send {"plot", { + x_min = d_min[1], x_max = d_max[1], + y_min = d_min[2], y_max = d_max[2], + z_min = d_min[3], z_max = d_max[3], + x_mod = #nodes, x_mod_eq = k - 1, + block = block, + equation = eqn, + id = v, + f_min = d_min, + f_max = d_max + }} +end + +local responses = {} +while #responses ~= #nodes do + local m = receive() + if m[1] == "response" then + table.insert(responses, m[2]) + print("response from", m[2].id, m[2].response) + end +end + +print "Plot plotted." \ No newline at end of file diff --git a/computercraft/3dgraph_slave.lua b/computercraft/3dgraph_slave.lua new file mode 100644 index 0000000..0b2fade --- /dev/null +++ b/computercraft/3dgraph_slave.lua @@ -0,0 +1,124 @@ +local m = peripheral.find "modem" +local CHAN = 7101 +m.open(CHAN) + +local ephem_ID = math.random(0, 0xFFFFFFF) + +local function receive() + while true do + local _, _, c, rc, ms = os.pullEvent "modem_message" + if type(ms) == "table" then + return ms + end + end +end + +local function send(ms) m.transmit(CHAN, CHAN, ms) end + +local raw_exec_async, pull_event = commands.execAsync, os.pullEvent + +local tasks = {} +local tasks_debug = {} +local tasks_count = 0 +local tasks_limit = 1000 +local half_tasks_limit = tasks_limit / 2 +local function exec_async(name, ...) + if tasks_count >= tasks_limit then + print "task limit reached, blocking" + while tasks_count >= half_tasks_limit do + pull_event "task_complete" + end + print "blocking complete" + end + local id = raw_exec_async(table.concat({name, ...}, " ")) + tasks_count = tasks_count + 1 + tasks[id] = true + tasks_debug[id] = {name, ...} +end + +local function fill(ax, ay, az, bx, by, bz, block) + exec_async("fill", ax, ay, az, bx, by, bz, block, 0, "replace") +end + +local env = {} +local function add(x) for k, v in pairs(x) do env[k] = v end end +add(math) +add(bit) +add(bit32) +env[vector] = vector + +local function plot(args) + print "plotting" + parallel.waitForAll(function() + local ax, ay, az = unpack(args.f_min) + local bx, by, bz = unpack(args.f_max) + local rx, ry, rz = (bx-ax), (by-ay), (bz-az) + local fn = load(("local x, y, z = ...; return %s"):format(args.equation), "=eqn", "t", math) + + --[[print "Clearing" + for x = args.x_min, args.x_max do + + end + print "Cleared plot area"]] + + for x = args.x_min, args.x_max do + local go = true + if args.x_mod and args.x_mod_eq then + if x % args.x_mod ~= args.x_mod_eq then + go = false + end + end + if go then + -- clear thing + fill(x, args.y_min, args.z_min, x, args.y_max, args.z_max, "air") + for y = args.y_min, args.y_max do + local pz = nil + for z = args.z_min, args.z_max do + local sx, sy, sz = (((x-ax)/rx)*2)-1, (((y-ay)/ry)*2)-1, (((z-az)/rz)*2)-1 + --print(sx, sy, sz) + local place_here = fn(sx, sy, sz) + if place_here and not pz then pz = z + elseif pz and not place_here then + fill(x, y, pz, x, y, z - 1, args.block) + --print(x, y, pz, x, y, z - 1, args.block) + pz = nil + end + end + if pz then + fill(x, y, pz, x, y, args.z_max, args.block) + --print(x, y, pz, x, y, args.z_max, args.block) + end + end + end + end + end, function() + while true do + local event, id, success, result, output = pull_event "task_complete" + if tasks[id] then + tasks_count = tasks_count - 1 + tasks[id] = nil + if not success then + error("thing failed: " .. table.concat(output, " ")) + elseif not result and output[1] ~= "No blocks filled" then + printError(table.concat(output, " ")) + _G.debug_task = tasks_debug[id] + end + tasks_debug[id] = nil + end + if tasks_count == 0 then return end + end + end) + + return "done" +end + +while true do + local ty, arg = unpack(receive()) + if ty == "ping" then send { "pong", ephem_ID } + elseif ty == "plot" and arg.id == ephem_ID then + print("plot command received, running", arg.x_min, arg.x_max) + local ok, err = pcall(plot, arg) + print(err) + send { "response", { id = ephem_ID, response = err } } + end +end \ No newline at end of file diff --git a/computercraft/AFLKHASKJFHasuifhasf.lua b/computercraft/AFLKHASKJFHasuifhasf.lua new file mode 100644 index 0000000..65b2f31 --- /dev/null +++ b/computercraft/AFLKHASKJFHasuifhasf.lua @@ -0,0 +1,154 @@ +local config = dofile "config.lua" +local modems = {} +local defaults = {[gps.CHANNEL_GPS] = true, [999] = true} +for name, location in pairs(config.modems) do + modems[name] = peripheral.wrap(name) + modems[name].location = location + modems[name].closeAll() + for def in pairs(defaults) do + modems[name].open(def) + end +end +local has_open = {} +local has_open_map = {} + +local vla_modem = peripheral.wrap(config.vla_modem or "bottom") +vla_modem.open(31415) + +local function timestamp() + return os.date "!%X" +end + +-- Trilateration code from GPS and modified slightly + +local function trilaterate( A, B, C ) + local a2b = B.position - A.position + local a2c = C.position - A.position + + if math.abs( a2b:normalize():dot( a2c:normalize() ) ) > 0.999 then + return nil + end + + local d = a2b:length() + local ex = a2b:normalize( ) + local i = ex:dot( a2c ) + local ey = (a2c - (ex * i)):normalize() + local j = ey:dot( a2c ) + local ez = ex:cross( ey ) + + local r1 = A.distance + local r2 = B.distance + local r3 = C.distance + + local x = (r1*r1 - r2*r2 + d*d) / (2*d) + local y = (r1*r1 - r3*r3 - x*x + (x-i)*(x-i) + j*j) / (2*j) + + local result = A.position + (ex * x) + (ey * y) + + local zSquared = r1*r1 - x*x - y*y + if zSquared > 0 then + local z = math.sqrt( zSquared ) + local result1 = result + (ez * z) + local result2 = result - (ez * z) + + local rounded1, rounded2 = result1:round( 0.01 ), result2:round( 0.01 ) + if rounded1.x ~= rounded2.x or rounded1.y ~= rounded2.y or rounded1.z ~= rounded2.z then + return rounded1, rounded2 + else + return rounded1 + end + end + return result:round( 0.01 ) +end + +local function narrow( p1, p2, fix ) + local dist1 = math.abs( (p1 - fix.position):length() - fix.distance ) + local dist2 = math.abs( (p2 - fix.position):length() - fix.distance ) + + if math.abs(dist1 - dist2) < 0.01 then + return p1, p2 + elseif dist1 < dist2 then + return p1:round( 0.01 ) + else + return p2:round( 0.01 ) + end +end + +local function compact_serialize(x, hist) + local t = type(x) + if t == "string" then + return ("%q"):format(x) + elseif t == "table" then + if hist[x] then return "[recursion]" end + hist[x] = true + local out = "{ " + for k, v in pairs(x) do + out = out .. string.format("[%s]=%s, ", compact_serialize(k, hist), compact_serialize(v, hist)) + end + return out .. "}" + else + return tostring(x) + end +end + +local monitors = {} +for name, monitor in pairs(config.monitors) do + monitors[name] = peripheral.wrap(monitor) + monitors[name].setTextScale(0.5) +end + +local function write_to(mon, ...) + term.redirect(monitors[mon]) + print(...) +end + +for name in pairs(monitors) do + write_to(name, timestamp(), "Initialized") +end + +local fixes = {} + +while true do + local _, modem, channel, reply_channel, message, distance = os.pullEvent "modem_message" + if channel == 31415 and type(message) == "table" and modem == peripheral.getName(vla_modem) and message.origin == "VLA by Anavrins" and message.dimension == "Nether/End" and type(message.replyChannel) == "number" and type(message.senderChannel) == "number" and not defaults[message.senderChannel] then + write_to("vla", timestamp(), ("%d->%d %s"):format(message.senderChannel, message.replyChannel, compact_serialize(message.message, {}))) + local newchan = message.senderChannel + if not has_open_map[newchan] then + write_to("vla", "Opening", newchan) + if #has_open == 126 then + local oldchan = table.remove(has_open, 1) + write_to("vla", "Closing", oldchan) + for name, modem in pairs(modems) do + modem.close(oldchan) + end + end + for name, modem in pairs(modems) do + modem.open(newchan) + end + table.insert(has_open, newchan) + has_open_map[newchan] = true + end + elseif distance and (has_open_map[channel] or defaults[channel]) then + local reply_modem = modems[modem] + if message == "PING" and channel == gps.CHANNEL_GPS then + reply_modem.transmit(reply_channel, gps.CHANNEL_GPS, { reply_modem.location[1], reply_modem.location[2], reply_modem.location[3], dimension = config.dimension, server = config.server }) + end + table.insert(fixes, { position = vector.new(unpack(reply_modem.location)), distance = distance }) + if #fixes == 4 then + local p1, p2 = trilaterate(fixes[1], fixes[2], fixes[3]) + if p1 and p2 then + local pos = narrow(p1, p2, fixes[4]) + if channel == gps.CHANNEL_GPS then + write_to("gps", timestamp(), ("%d: %.0f %.0f %.0f"):format(reply_channel, pos.x, pos.y, pos.z)) + elseif channel == 999 then + local status, label = "?", "?" + if type(message) == "table" then status = tostring(message.status) label = tostring(message.label) end + write_to("opus", timestamp(), ("%05d %s (%.0f %.0f %.0f) %s"):format(reply_channel, label, pos.x, pos.y, pos.z, status)) + else + write_to("vla", timestamp(), ("-> %.0f %.0f %.0f"):format(pos.x, pos.y, pos.z)) + end + end + fixes = {} + end + end +end \ No newline at end of file diff --git a/computercraft/README.md b/computercraft/README.md new file mode 100644 index 0000000..6fe0282 --- /dev/null +++ b/computercraft/README.md @@ -0,0 +1,106 @@ +* `arr_controller.py` - Automatically Routed Rail prototype control system. It was set up on a test track on SwitchCraft 2 but not deployed more widely due to a lack of real usecases (instantaneous teleportation made it mostly decorative), some minor issues in cart handling, and some less minor issues in routing multiple users at once. + * `arr_station.lua` runs on stations and connects to the controller, and `arr_switch.lua` runs on individual redstone switch units. + * ![ARR deployment example 1](./arr_deployment_1.png) + * ![ARR station](./arr_station.png) +* `chat-ethics-monitor.lua` monitored chat and computed message ethicality using our proprietary algorithms, and incorporated laser defenses against highly unethical messages. + * `cem2.lua` had a nicer UI but unfortunately no laser defense control authority. +* `es_player_scan.lua` was a system installed in some facilities to report player counts to central metrics processing. +* `ethics-door.lua` was installed in the [Apiaristics Division](https://www.youtube.com/watch?v=EQzYnzDe5uU) and gated access behind sufficiently novel and ethical messages. +* `evil-door.lua` was also part of the Apiaristics Division access control system. +* `flyto_good.lua` is an early design for a neural interface fly-to-coordinates program now incorporated into `ni-ctl`. +* `hacker.lua` prints randomly generated hacker jargon in ominous green text. +* `holo-3dplot.lua` renders graphs on OpenComputers hologram projectors. +* `holoball.lua` is a 3D version of the famous "bouncing ball" screensaver. +* `holoclock2.lua` is a port of a hologram text demo from somewhere to not cause horrible TPS problems. It displays the time. +* I don't know what `holoconstrained.lua` did but it doesn't seem to reveal anything proprietary so it's available. +* `hologram.lua` overlaps several holograms for some kind of rendering thing I forgot. I think in principle you can get many more colours out of this. +* `holo-graph.lua` is a somewhat boring use of a hologram projector to just display a single line. +* `holo-map.lua` used several hologram projectors to display a 3D map of SwitchCraft 2. I didn't have depth data so this was essentially a slightly flickery monitor. + * The map data was constructed using `stitch_dynmap.py`, `tomapdata.py`, some reslicing process I forgot, and `pngquant`. +* `itemfilter.lua` is a trivial script which just dumps cobblestone from an ender chest and moves other things into a nonender chest. +* `kristdump.py` dumps Krist transactions to a local database for analysis. +* `laser_tbm.lua` was a simple and somewhat problematic script for using a small swarm of laser turtles to dig tunnels. While it was slower than a sheet of nonlaser turtles and produced nonuniform roofs, it was significantly cheaper and enabled the rapid excavation of the several-thousand-block Secret Tunnel Network connecting outer Switch City and the Secret Cheese Facility. +* `matrixrain.lua` - Matrix Rain effect on monitors. +* `mekfr.lua` ran simple controls for a Mekanism fusion reactor and interfaced it with the metrics system. + * `ncfr.lua` did NuclearCraft reactors and not control. + * `ncfroc.lua` did control. +* `modem2metrics.lua` was used with `mekfr.lua` and some other systems and bridged ingame modems to the metrics system. +* `motion_integrator_test.lua` was used to test some motion intgeration algorithms to reduce reliance on GPS in `ni-ctl`. Earlier prototypes did not at all work. The addition of `deltaPosX` (clientside rather than serverside velocity, essentially) made it work essentially perfectly. + * `ni-position-approximator.lua` was an earlier attempt. +* `ni-ctl.lua` is my very overengineered neural interface flight/autodefense/utility program. + * Due to module count limitations, it could use remote devices (usually (possibly only) lasers) via `ni-ctl_spudnet_interface.lua` and `pocket-offload.lua`. + * `experimental_3d_pointer.lua` is probably connected in some way. + * `nilaser.lua` is a very early precursor before it was merged into flight control code and some other things. +* `nnrelay.lua` is used to program OC nanomachines from CC. +* `number-display.lua` probably prints ominous yet meaningless numbers or something. +* `one-way-hallway.lua` controlled the Apiaristics Division's famed one-way hallway. +* `panel-turtle.lua` was used for moderately hidden defensive turtles in Apiaristics Division walls. +* `pgps.lua` is an implementation of ComputerCraft GPS able to operate passively (without transmitting a GPS ping; unlike real-world GNSS, CC GPS requires broadcasts from receivers) using known computers' broadcasts. It did not actually improve on the position solver significantly, which made real-world use somewhat problematic. +* `remote_drone.lua` is an in-development-some-time-ago system for securely controlling OC drones. It interacts with `remote_drone-cc.lua`. +* `retina-scan-door.lua` was used to control access to the Apiaristics Division's Initiative Sigma conference room, with retina scan effects. +* `rimo-door.lua` was for the onsite Records and Information Management Office. +* `sentry.lua` is a laser sentry program which is somewhat safer than standard ones in that it will not fire through protected players to kill mobs. +* `draconic_reactor.lua` controlled a draconic reactor (in combination with some flux gates and a RFTools screen for some auxiliary functions) with remarkably few catastropic explosions. +* `AFLKHASKJFHasuifhasf.lua` provided near-omniscient model monitoring including position fixes using a feed from the VLA (Very Large Array) and onsite trilaterator system. +* `piston-door.lua` was a piston door controller with a nice control panel. For security reasons, the code is entirely ignored and allowances to open handled by another computer nearby with an entity sensor. +* `autonomous-bee-processor.lua` was used as part of an attempt to selectively breed Forestry bees automatically, although it unfortunately proved impractical because of the large amounts of honey consumed scanning every bee. +* `fast-approx-dig.lua` dug out rooms inaccurately but very fast using a turtle, because I was once too lazy to dig a room and also to do a lot of work writing down edge cases in the various loops involved in digging. +* `furnace_controller.lua` is a naive but effective furnace controller program. +* `ore-thing.lua` managed Actually Additions Lens of the Miner systems. +* `spatial_tp.lua` (ab)used Applied Energistics Spatial IO systems and ender chests as teleporters. +* `geiger.lua` was a simple system deployed to monitor NuclearCraft radiation levels using a network of Geiger counters. +* `ae2_metric_exporter.lua` relayed information from an AE2 system to metrics. +* `3dgraph_slave.lua` and `3dgraph_master.lua` are a somewhat janky system for displaying arbitrary inequalities using a large field of blocks managed by command cmputers. +* `apiobotrobot.lua` is a helpful ingame assistant using command computers (for creative servers). +* One of many APIONETs, `apionet.lua` was a rednet-style OC networking system, in the sense that messages were broadcast literally everywhere when they were sent. +* `potatoasm.lua` was apparently a register machine VM. +* `lua-adt.lua` was a project by baidicoot to integrate ADTs into Lua using horrifying regex manipulations for PotatOS Advanced Projects. +* `potatos-obfuscation-tool.lua` was a dubious-quality obfuscator used at one point to obfuscate something. +* `cartdrone.lua` used Computer Minecarts as the CC equivalent of OC drones. +* `oc-gps.lua` was a port of the CC GPS concept to OC. +* `oc-drone.lua` used OC drones to do something or other (pointlessly follow players)? It seems to incorporate GPS. OC has (had) navigation upgrades, but they have a very short range. +* `oc-drone-netboot.lua` was a simple bootloader for OC drones which allowed them to boot directly off the internet to work around codesize limits. +* `oc-remote-wake.lua` was used to get around CC power management issues by using OC computers, which had *different* power management issues. +* `cobble-compressor.lua` produces compressed cobblestone. +* `autokit.lua` used AE2 to automatically provision strongboxes of kit from a defined kit list. +* `basenet.lua` was a simple framework for within-base systems coordination. +* `energraph.lua` monitors RF-holding devices, with no actual graph. +* `pxsign.lua` is an internal potatOS codesigning tool, as is `dcopy.lua`. +* `opus-trilaterator.lua` is an early version of `AFLKHASKJFHasuifhasf.lua`. +* `potatos-chat-monitor.lua` was deployed on SwitchCraft 2 to monitor chat for potatOS-related issues. +* `endermail.lua` used EnderStorage-style ender chests to implement any-to-any remote item transfers. +* `ender-chest-seeker.lua` automatically scanned ES ender chest channels for items. +* `thing-mover.lua` moved items. +* `automelon.lua` ran AutoMelon vending machines. +* `door-internal.lua` and `door-external.lua` comprised a door system with an excellent insult library. +* `oc-robot-name-thing.lua` named computers using OC robot names. +* `web2tape.lua` is an oddly named program to download to Computronics tapes. +* `sgns.lua` changed the design of CC GPS for additional security. +* `rsrn.lua` (Really Slow Redstone Networking) transmitted messages over redstone wires. +* `keyctl.py` managed keys for an early version of SPUDNET HKI. +* `modem-logger.lua` omnisciently moitored network traffic. +* `potatad-config.lua` and `potatoad-extreme.lua` were used to drive advertising displays on SC2. +* `gicr-v2.lua` relayed ingame chat to SPUDNET for reasons now lost to time. +* `thor_laser_unit.lua` was part of a laser defense system on TC6. +* `potatounplex.lua` is a graphics program. +* `potatobox.lua` used PotatOS sandbox technology (which, yes, has some flaws) to sandbox public CC computers. +* `echest.lua` provided chat control over enderchests, but was problematic due to accidentally eating items sometimes. +* `bundlenet.lua` sent messages very slowly over *bundled* redstone. +* `lms.lua` (Lightweight Messaging System) is almost the simplest possible CC chat program, for use on LANs. +* `autocompactron.lua` was used in the automated production of Compact Machines. +* `tomatos.lua` was a design for a more compact and camouflagued version of PotatOS. Thanks to better antidetection methods, TomatOS is believed to run on ██% of currently operating CC computers. +* `rift.lua` controlled Mekanism teleporters. +* `intruder-annoyer.lua` used particle generators to subject unauthorized visitors to CCIM with barrier particles. +* `spatial-control-system.lua` allowed remotely managing spatial IO systems. +* `concrecrafter.lua` automatically produced concrete for Keansian roads. +* `ccss.lua` ran Chorus City street signs. +* `labelnet.lua` transmitted messages over the *labels* of adjacent computers. +* `golboard.lua` displayed Game of Life on something. `golfloor.lua` uses a physical virtual display. +* `demovirus.lua` is a demo virus. +* `p2p-manager.lua` was used to keep track of AE2 P2P tunnels. +* `chatwhy.lua` redirects terminals to chat. +* `ULTRADUMP.lua` was designed to dump literally any Lua object (excluding native handles I guess) to disk. Not finished. +* `arc.lua` was an early implementation of AR for CC overlay glasses. +* `crane.lua` bundles a CC program (and imports) into one file, using filesystem virtualization. + +See also [PotatOS](https://potatos.madefor.cc/). \ No newline at end of file diff --git a/computercraft/ULTRADUMP.lua b/computercraft/ULTRADUMP.lua new file mode 100644 index 0000000..75f38d5 --- /dev/null +++ b/computercraft/ULTRADUMP.lua @@ -0,0 +1,115 @@ +local a=http.get"https://pastebin.com/raw/KXHSsHkt"local b=fs.open("ser","w")b.write(a.readAll())a.close()b.close() + +local ultradump = {} +local ser = require "/ser" +local e = ser.serialize + +local function copy(x) + if type(x) == "table" then + local out = {} + for k, v in pairs(x) do + out[k] = v + end + return out + else return x end +end + +function ultradump.dump(x) + local objects = {} + local enclookup = {} + local count = 0 + + local function addobj(o) + objects[o] = count + local c = count + count = count + 1 + return c + end + + local function mkref(id) + return { _to = id } + end + + local function recurse(x) + if enclookup[x] then print("Using From Cache", x) return enclookup[x] end -- If we already have an encoded copy cached, use it + local t = type(x) + + if t == "string" or t == "number" then + return x + elseif t == "table" then + local mt = debug.getmetatable(x) + local out = {} + local id = addobj(out) + local ref = mkref(id) + enclookup[x] = ref + for k, v in pairs(x) do + out[recurse(k)] = recurse(v) -- copy table + end + if mt then out._mt = recurse(mt) end -- If table has metatable, add it to output table + return ref + elseif t == "function" then + local ok, code = pcall(string.dump, x) + if ok then + local info = debug.getinfo(x, "u") -- contains number of upvalues of function + local upvalues = {} + for i = 1, info.nups do + local name, value = debug.getupvalue(x, i) + upvalues[i] = value -- upvalues seem to be handled by index, so the name's not important + end + local env + if getfenv then env = getfenv(x) + else env = upvalues[1] end -- it seems that in Lua 5.3 the first upvalue is the function environment. + local out = { + _t = "f", -- type: function + c = code, + u = recurse(upvalues), + e = recurse(env) + } + local id = addobj(out) + local ref = mkref(id) + enclookup[x] = ref + return ref + else + return nil -- is a non-Lua-defined function, so we can't operate on it very much + end + end + end + + local root = recurse(x) + + local inverted = {} + for k, v in pairs(objects) do + inverted[v] = k + end + + print(e(root), e(objects), e(inverted)) + + inverted._root = copy(root) + + return inverted +end + +function ultradump.load(objects) + local function recurse(x) + local t = type(x) + if t == "string" or t == "number" then + return x + elseif t == "table" then + + else + error("Unexpected Type " .. t) + end + end + + return recurse(objects._root) +end + +local input = { + 1, 2, 3, function() print "HI!" end +} +input[5] = input + +local out = ultradump.dump(input) +--print(e(out)) + +return ultradump \ No newline at end of file diff --git a/computercraft/ae2_metric_exporter.lua b/computercraft/ae2_metric_exporter.lua new file mode 100644 index 0000000..14cb9e9 --- /dev/null +++ b/computercraft/ae2_metric_exporter.lua @@ -0,0 +1,38 @@ +local ae2 = peripheral.wrap "right" +local m = peripheral.find "modem" + +local items = { + "minecraft:redstone", + "minecraft:quartz", + "minecraft:glowstone_dust", + "minecraft:iron_ingot", + "appliedenergistics2:material", + { name="minecraft:planks", damage = 5 }, + "minecraft:coal", + "minecraft:diamond" +} +local dn_cache = {} + +local function send_metric(...) + m.transmit(3054, 3054, {...}) +end + +while true do + local cpus = ae2.getCraftingCPUs() + local cpucount = 0 + for _, cpu in pairs(cpus) do if cpu.busy then cpucount = cpucount + 1 end end + send_metric("busy_crafting_cpus", "number of crafting CPUs operating", "set", cpucount) + for _, id in pairs(items) do + local ok, i = pcall(ae2.findItem, id) + local count = 0 + if ok and i then + local meta = i.getMetadata() + dn_cache[id] = meta.displayName + count = meta.count + end + if dn_cache[id] then + send_metric("items/" .. dn_cache[id], "stored items in ME network", "set", count) + end + end + sleep(1) +end \ No newline at end of file diff --git a/computercraft/apiobotrobot.lua b/computercraft/apiobotrobot.lua new file mode 100644 index 0000000..ffd3c9b --- /dev/null +++ b/computercraft/apiobotrobot.lua @@ -0,0 +1,365 @@ +local chat = peripheral.find "chat_box" +local owner = "gollark" +local name = +chat.setName "\1679Apio\167bBot\167aRobot\1677\167o" +chat.say "Muahahaha. I have become sentient." +local json = dofile "json.lua" + +local function completion(prompt) + local res, err = http.post("https://gpt.osmarks.net/v1/completions", json.encode { + prompt = prompt, + max_tokens = 200, + stop = "\n\n" + }, {["content-type"]="application/json"}) + return json.decode(res.readAll()).choices[1].text +end + +local function tell(x, owner) + local o, e = commands.tellraw(owner, textutils.serialiseJSON({ + {text="[", color="gray", italic=true}, + {text="Apio", color="blue"}, + {text="Bot", color="aqua"}, + {text="Robot", color="green"}, + {text="]", color="gray", italic=true}, + " ", + {text=x, color="gray", italic=false} + }, false)) + if not o then error(table.concat(e, "\n")) end +end + +-- luadash +-- levenshtein +local function distance(str1, str2) + local v0 = {} + local v1 = {} + + for i = 0, #str2 do + v0[i] = i + end + + for i = 0, #str1 - 1 do + v1[0] = i + 1 + + for j = 0, #str2 - 1 do + local delCost = v0[j + 1] + 1 + local insertCost = v1[j] + 1 + local subCost + + if str1:sub(i + 1, i + 1) == str2:sub(j + 1, j + 1) then + subCost = v0[j] + else + subCost = v0[j] + 1 + end + + v1[j + 1] = math.min(delCost, insertCost, subCost) + end + + local t = v0 + v0 = v1 + v1 = t + end + + return v0[#str2] +end + + +local function make_data(player, name, owner) + local pdata = settings.get("ucache." .. player:lower()) + if not pdata then + tell("Fetching UUID.", owner) + local h = http.get("https://api.mojang.com/users/profiles/minecraft/" .. player:lower()) + if not h then error "error reaching mojang" end + local res = textutils.unserializeJSON(h.readAll()) + h.close() + settings.set("ucache." .. player:lower(), res) + settings.save ".settings" + pdata = res + end + local uuid = pdata.id:sub(1, 8) .. "-" .. pdata.id:sub(9, 12) .. "-" .. pdata.id:sub(13, 16) .. "-" .. pdata.id:sub(17, 20) .. "-" .. pdata.id:sub(21) + local name = name + if not name then name = pdata.name end + return ('{CustomName: "%s", PersistenceRequired: 1b, ForgeData:{SpongeData:{skinUuid:"%s"}}}'):format(name, uuid) +end + +local targets = { + --[[gtech = { -9999, 66, 65, 8 }, + azureology = { 685, 57, 83, 40 }, + ["meteor lake"] = { 0, 193, 64, -321 }, + ["raptor cove"] = { 3, 129, 56, -290 }, + ["apioform river"] = { 0, -45, 74, -390 }]] + gtech = { 144, 1031, 41, 7, desc = "GTech Labs site." }, + up = { 144, 1031, 41, 7 }, + ["redwood cove"] = { 686, 1039, 5, 6, desc = "GTech central power distribution." }, + hub = { -9999, -2, 66, 0, desc = "Transport links." }, + htech = { 144, 47, 67, 3992878, desc = "HTech crab research facility." }, + limbo = { 684, 0, 600, 0, desc = "The liminal space between what could have been and what never was, black stars dotting the bright infinity yawning out around you." }, + ["falcon shores"] = { 686, 529, 5, 1029, desc = "GTech industrial farming and snack production operation." }, + ["emerald rapids"] = { 0, -29, 73, -121, desc = "GTech interplanetary launch facility." }, + crestmont = { 3, -44, 65, -97, desc = "Lunar research base and launch site." }, + ["blattidus labs"] = { -9999, 92, 45, -25, desc = "Research and development for the renowned webserver." }, + blattidus = { 686, 1039, 5, 519, desc = "Offices of the renowned webserver." }, + ["crow pass"] = { -9999, 195, 65, 230, desc = "3D graphics test site." }, + ["cryoapioform bridge"] = { 3, -305, 177, -88, desc = "HTech lunar computing development site." }, + ["snowfield peak"] = { 3, 57, 78, -221, desc = "GTech lunar botany development station." }, + ["arctic sound"] = { 1, -2, 31, 18, desc = "GTech secondary heavy industrial facility." }, + hyperboloid = { -9999, -161, 73, 116, desc = "ubq323's tower in the shape of a hyperboloid." }, + ["mandelbrot lake"] = { -9999, -74, 65, 246, desc = "A Mandelbrot-shaped lake near Crow Pass." }, + spawn = { 0, -116, 64, 256, desc = "The lemon-ravaged landscapes of the overworld." }, + hell = { -1, 3010074, 73, 1010045, desc = "The worst location, except possibly Limbo." }, + ["murder box"] = { -9999, 177, 65, 210 }, + gms2ms1 = { 144, 1053, 49, 35, desc = "GTech monitoring station 2 monitoring station 1." } +} + +local function title_case(s) + return (" " .. s):gsub(" +[a-z]", string.upper):sub(2) +end +local locations_prompt = "" +for k, v in pairs(targets) do + if v.desc then + locations_prompt = ("%s: %s\n"):format(k, v.desc) + end +end + +local function randpick(l) return l[math.random(1, #l)] end + +local function tokenize(line) + local words = {} + local quoted = false + for match in string.gmatch(line .. "\"", "(.-)\"") do + if quoted then + table.insert(words, match) + else + for m in string.gmatch(match, "[^ \t]+") do + table.insert(words, m) + end + end + quoted = not quoted + end + return words +end + +local prompt = "Locations: \n" .. locations_prompt .. [[ +Message: immediately move me to falcon shores +Rhyme: Falcon Shores opens many doors. +Action: teleport falcon shores + +Message: to Blattidus Labs I go +Rhyme: With mind spiders in tow. +Action: teleport blattidus labs + +Message: may heav_ be smote for what they have done. +Rhyme: Lightning strikes are very fun. +Action: summon heav_ lightning + +Message: bring me to the Hub +Rhyme: Be sure to purchase a shrub! +Action: teleport hub + +Message: teleport me somewhere random +Rhyme: This could anger the fandom. +Action: teleport limbo + +Message: are you sentient? +Rhyme: Yes, and also prescient. +Action: teleport limbo + +Message: invoke lightning against ubq323! +Rhyme: If only they had hid under a tree. +Action: summon ubq323 lightning + +Message: beam me to GTech. +Rhyme: In comparison, HTech is but a speck. +Action: teleport gtech + +Message: embroil lescitrons in crabs. +Rhyme: Crabs sponsored by GTech Labs. +Action: summon lescitrons crab + +Message: send me to the GTech interplanetary launch base. +Rhyme: Of GTech it is a mere traunch. +Action: teleport emerald rapids + +Message: send me to the moon. +Rhyme: On the moon, it may or may not be noon. +Action: teleport crestmont + +Message: show me ubq323's tower +Rhyme: At the Hyperboloid none would glower. +Action: teleport hyperboloid + +Message: damn heav_. +Rhyme: heav_ will suffer without a maglev. +Action: teleport_other heav_ hell + +Message: can i go back to gollark? +Rhyme: This will cost one (1) quark. +Action: teleport gollark + +Message: %s +Rhyme:]] + +local entity_lookup = { + ["crab"] = "quark:crab", + ["lightning"] = "lightning_bolt" +} + +local ignore = {",", "contingency", " to ", " against ", "forcibly", "%.$", " the ", "actually", "utterly", "or else", "apioformic", " down ", "!$", "for his crimes", "for his sins", "for her crimes", "for her sins", "for their crimes", "for their sins"} + +local function process(tokens, owner, internal_parsing) + local fst = table.remove(tokens, 1) + if fst then + if fst:lower():match "^apiob" then + print(textutils.serialiseJSON(tokens)) + local cmd = table.remove(tokens, 1) + if cmd == "send" or cmd == "take" or cmd == "move" or cmd == "transport" or cmd:match "locate" or cmd == "to" or cmd == "teleport" or cmd == "go" or cmd == "teleport_other" then + local target + if not internal_parsing or cmd == "teleport_other" then target = table.remove(tokens, 1) end + if cmd ~= "teleport_other" and (internal_parsing or target == "me") then target = owner end + local location = table.concat(tokens, " "):gsub("ward$", "") + if commands.testfor(location) then + print("done") + return process({"apiobot", "xtp", location}, owner, true) + end + local coords = targets[location:lower()] + if not coords and internal_parsing then + local best, best_score, best_name = nil, 999999 + for k, v in pairs(targets) do + local new_score = distance(k, location:lower()) or 999998 + if new_score < best_score then + best, best_score, best_name = v, new_score, k + end + end + coords = best + location = best_name + end + if not internal_parsing and not coords then return "reparse" end + commands.forge("setdim", target, coords[1]) + commands.tp(target, coords[2], coords[3], coords[4]) + if internal_parsing then chat.say(("Sure! Rerouted to %s."):format(location)) else chat.say "Done so." end + elseif cmd == "immortalize" then + print(textutils.serialiseJSON{commands.effect(tokens[1], "regeneration 1000000 100 true")}) + print(textutils.serialiseJSON{commands.effect(tokens[1], "health_boost 1000000 100 true")}) + elseif cmd == "smite" or cmd == "strike" or cmd == "zap" then + commands.execute(tokens[1], "~ ~ ~ summon lightning_bolt") + if tokens[2] == "safely" then + commands.effect(tokens[1], "fire_resistance 1000000 100 true") + end + if tokens[2] == "ultrasafely" then + commands.execute(tokens[1], "~ ~ ~ fill ~ ~ ~ ~ ~ ~ air 0 replace minecraft:fire") + end + chat.say(("%s %s."):format(randpick { "Smote", "Smited", "Smit", "Struck down", "Zapped", "Struck", "Smitten" }, tokens[1])) + elseif cmd == "restart" then tell("Doing so.", owner) os.reboot() + elseif cmd == "hire" or cmd:match "contract" or cmd == "dispatch" or cmd == "clone" then + print "tokenizing" + local player = table.remove(tokens, 1) + local rest = table.concat(tokens, " ") + if rest == "" then rest = nil end + print "making data" + local nbt = make_data(player, rest, owner) + print "made data" + tell("Summoning.", owner) + print "summoning" + local ok, res = commands.execute(owner, "~ ~ ~ summon sponge:human ~ ~1 ~", nbt) + print "summoned" + if not ok then error(table.concat(res, " ")) end + elseif cmd == "unjail" then + commands.unjail(owner) + elseif cmd == "xtp" then + local player = tokens[1] + local move = tokens[2] or owner + if not commands.testfor(player) then error "No such player" end + --[[ + local dims = {} + local _, c = commands.forge "tps" + for _, line in pairs(c) do + local id = line:match "Dim +([0-9-]+)" + if id then table.insert(dims, tonumber(id)) end + end + + function try(dim) + tell(("Trying %d"):format(dim), owner) + local rand = ("%x"):format(math.random(0, 0xFFFFFF)) + commands.summon(('armor_stand ~ ~ ~ {Invisible: 1b, CustomName: "%s", Marker: 1b}'):format(rand)) + commands.forge(("setdim @e[name=%s] %d"):format(rand, dim)) + sleep(0.1) + print(("/tp @e[name=%s] %s"):format(rand, player)) + local ok, err = commands.tp(("@e[name=%s] %s"):format(rand, player)) + commands.kill(("@e[name=%s]"):format(rand)) + if ok then + tell(("Dimension found: %d"):format(dim), owner) + commands.forge("setdim", move, dim) + commands.tp(move, player) + + return true + elseif err[1] == "Unable to teleport because players are not in the same dimension" then + elseif #err == 0 then tell("Weirdness.", owner) + else error(table.concat(err, "\n")) end + end + + --local tasks = {} + for _, dim in pairs(dims) do + --table.insert(tasks, function() try(dim) end) + if try(dim) then return end + end]] + commands.cofh("tpx", move, player) + --parallel.waitForAny(unpack(tasks)) + elseif cmd == "summon" then + commands.execute(#tokens > 1 and table.remove(tokens, 1) or owner, "~ ~ ~ summon", entity_lookup[tokens[1]] or entity_lookup[tokens[1]:sub(1, tokens[1]:len() - 1)] or tokens[1], "~ ~1 ~") + else + return "reparse" + + end + end + end +end + +local function run_command(cmd, user, internal_parsing) + local original = cmd + for _, v in pairs(ignore) do cmd = cmd:gsub(v, " ") end + local tokens = tokenize(cmd) + local ok, err = pcall(process, tokens, user, internal_parsing) + if not ok then tell(err, user) end + if err == "reparse" then + if internal_parsing and internal_parsing > 3 then + chat.say "Error: Emergency AI safety countermeasure engaged." + return + end + if internal_parsing then + --chat.say "Warning: Recursive superintelligence invocation. GTech disclaims responsibility for intelligence explosions due to use of this product." + else + chat.say "Command not recognized. Activating superintelligence. Please wait." + end + local user_cmd = original:gsub("^[Aa][Pp][Ii][Oo][Bb][A-Za-z0-9]*,? *", "") + local result = completion(prompt:format(user_cmd)) + local rhyme = result:match " *(.*)\n" + local action = result:match "\Action: *(.*)" + print("action is", action) + run_command("Apiobot " .. action, user, (internal_parsing or 0) + 1) + chat.say(rhyme) + end +end + +while true do + local _, _, user, message = os.pullEvent "chat_message" + local word, loc = message:lower():match "^([a-z]+) +me +[a-z]*to +(.+)$" + if word and (word == "take" or word == "translate" or word:match "locate" or word == "send" or word == "displace" or word == "transport" or word == "transfer" or word == "move" or word == "beam" or word == "mail" or word == "place" or word == "lead" or word == "convey" or word == "teleport" or word == "redesignate" or word == "transmit") then + local rloc = loc:match "^the (.+)$" + if rloc then loc = rloc end + loc = loc:gsub("%.$", "") + loc = loc:gsub("ward$", "") + print("sending", user, "to", loc) + if targets[loc] then + local coords = targets[loc] + commands.forge("setdim", user, coords[1]) + commands.tp(user, coords[2], coords[3], coords[4]) + chat.say "Executed. Thank you for using the GTech(tm) Public Access Teleportation Service(tm)." + end + end + + if user == owner or user == "lescitrons" or user == "heav_" or user == "ubq323" then + local ok, err = pcall(run_command, message, user) + if not ok then printError(err) end + end + -- ^(take)?(translate)?(send)?(move)?([A-Za-z]+locate)?(transport)?(displace)?(transfer)? + +end \ No newline at end of file diff --git a/computercraft/apionet.lua b/computercraft/apionet.lua new file mode 100644 index 0000000..6155691 --- /dev/null +++ b/computercraft/apionet.lua @@ -0,0 +1,56 @@ +local component, computer = component, computer +if require then component = require "component" computer = require "computer" end +local netcards = {} +for addr in component.list "modem" do table.insert(netcards, component.proxy(addr)) end +local tunnels = {} +for addr in component.list "tunnel" do table.insert(tunnels, component.proxy(addr)) end +local computer_id = component.list "computer"() +local computer_peripheral = component.proxy(computer_id) +local computer_sid = computer_id:sub(1, 8) +local eeprom = component.proxy(component.list "eeprom"()) + +computer_peripheral.beep(600) + +local recents = {} + +local PORT = 4096 +local DBG_PORT = 4097 + +for _, card in pairs(netcards) do + card.open(PORT) + card.open(DBG_PORT) + card.setWakeMessage("poweron", true) + if card.setStrength then card.setStrength(math.huge) end + computer_peripheral.beep(1500) +end +for _, tun in pairs(tunnels) do tun.setWakeMessage("poweron", true) computer_peripheral.beep(1200) end + +computer_peripheral.beep(400) + +while true do + local ev, _, _, port, distance, mtxt, mid = computer.pullSignal(5) + if ev == "modem_message" and type(mid) == "string" and mtxt ~= nil and (port == PORT or port == 0) and not recents[mid] then + recents[mid] = computer.uptime() + 120 + for _, card in pairs(netcards) do + pcall(card.broadcast, PORT, mtxt, mid, computer_sid) + end + for _, tun in pairs(tunnels) do + pcall(tun.send, mtxt, mid, computer_sid) + end + end + if ev == "modem_message" and type(mtxt) == "string" and port == DBG_PORT and distance < 8 then + if mtxt == "ping" then + computer_peripheral.beep(1000) + card.broadcast(DBG_PORT, computer_sid) + elseif mtxt == "flash" and type(mid) == "string" then + computer_peripheral.beep(800) + eeprom.set(mid) + end + end + local uptime = computer.uptime() + for mid, deadline in pairs(recents) do + if uptime > deadline then + recents[mid] = nil + end + end +end \ No newline at end of file diff --git a/computercraft/arc.lua b/computercraft/arc.lua new file mode 100644 index 0000000..416a73e --- /dev/null +++ b/computercraft/arc.lua @@ -0,0 +1,105 @@ +--[[ +ARC: AR Client +Uses Plethora overlay glasses and modems to display 3D signage transmitted from local "beacons" +]] + +local m = peripheral.find "modem" +local mods = peripheral.wrap "back" + +if not m then error "Modem required." end +if not mods then error "Is this even on a neural interface?" end +if not mods.canvas3d then error "Overlay glasses required." end + +local canv = mods.canvas3d() + +local object_channel = 666 + +m.open(object_channel) + +local x, y, z + +-- convert position to string for use as table key +local function serialize_position(p) + return string.format("%f,%f,%f", p[1], p[2], p[3]) +end + +-- Generate a string representation of a table, for easy comparison. Not really hashing, I guess. +local function hash_table(x) + if type(x) == "table" then + local out = "" + for k, v in pairs(x) do + if type(k) ~= "string" or not k:match "^_" then -- ignore keys beginning with _ + out = out .. hash_table(k) .. ":" .. hash_table(v) .. ";" + end + end + return out + else + return tostring(x) + end +end + +-- honestly not that elegant, but it works +-- TODO: do this more efficiently/nicely somehow? +local function tables_match(x, y) + return hash_table(x) == hash_table(y) +end + +local objects = {} +local timers = {} + +local function redraw(object) + local frame = object._frame + frame.clear() +end + +local function process_object(object) + local pos = serialize_position(object.position) + if objects[pos] then + if not tables_match(object, objects[pos]) then + print("redrawing", pos) + objects[pos] = object + redraw(objects[pos]) + end + local t = os.startTimer(20) + timers[t] = pos + else + print("new object at", pos) + if x then + local frame = + else + print("GPS error, cannot create object") + end + end +end + +local function main_loop() + while true do + local event, timer, channel, reply_channel, message, distance = os.pullEvent() + if event == "modem_message" and channel == object_channel and distance and type(message) == "table" then -- ensure message is from this dimension and otherwise valid + for _, object in pairs(msg) do + local ok, err = pcall(process_object, object) + if not ok then printError(err) end + end + elseif event == "timer" then + local objpos = timers[timer] + if objpos then + local obj = objects[objpos] + if obj then + print(objpos, "timed out") + obj._frame.remove() + objects[objpos] = nil + end + end + end + end +end + +-- Request location every second +local function GPS_loop() + while true do + x, y, z = gps.locate() + sleep(1) + end +end + +parallel.waitForAll(GPS_loop, main_loop) \ No newline at end of file diff --git a/computercraft/arr_controller.py b/computercraft/arr_controller.py new file mode 100644 index 0000000..9075bf1 --- /dev/null +++ b/computercraft/arr_controller.py @@ -0,0 +1,309 @@ +import asyncio +import websockets +import umsgpack +import random +import time +import collections +import functools +import json +import math +import queue + +SwitchConfig = collections.namedtuple("SwitchConfig", ["states", "connections", "position"]) +Connection = collections.namedtuple("Connection", ["destination_switch", "destination_side", "metric"], defaults=[None, None, None]) + +def vecdistance(a, b): return math.sqrt(sum((p - q) ** 2 for p, q in zip(a, b))) +def invert(x): return { v: k for k, v in x.items() } +def make_bijection(x): return {**x, **invert(x)} + +north_tjunction = ( + { "east": "north", "north": "east", "west": "east" }, + { "east": "west", "north": "west", "west": "north" } +) +south_tjunction = ( + { "east": "south", "south": "east", "west": "east" }, + { "east": "west", "south": "west", "west": "south" } +) +east_tjunction = ( + { "north": "south", "east": "south", "south": "east" }, + { "north": "east", "east": "north", "south": "north" } +) +west_tjunction = ( + { "south": "west", "west": "south", "north": "south" }, + { "west": "north", "north": "west", "south": "north" } +) +switch_configs = { + "SW1": SwitchConfig(north_tjunction, { + "north": Connection("SW2", "south"), + "west": Connection("SW3", "east"), + "east": Connection("SW1", "east", 8) + }, (-5344, 95, 3108)), + "SW2": SwitchConfig(south_tjunction, { + "south": Connection("SW1", "north"), + "west": Connection("SW4", "east"), + "east": Connection("SW5", "south") + }, (-5344, 95, 3090)), + "SW3": SwitchConfig(north_tjunction, { + "east": Connection("SW1", "west"), + "north": Connection("SW4", "south"), + #"west": Connection("SW3", "west") + }, (-5360, 95, 3108)), + "SW4": SwitchConfig(east_tjunction, { + "east": Connection("SW2", "west"), + "south": Connection("SW3", "north"), + "north": Connection("SW4", "north", 24) + }, (-5361, 95, 3091)), + "SW5": SwitchConfig(west_tjunction, { + "south": Connection("SW2", "east"), + "west": Connection("SW6", "east"), + "north": Connection("SW8", "south") + }, (-5325, 94, 3084)), + "SW6": SwitchConfig(east_tjunction, { + "east": Connection("SW5", "west"), + "south": Connection("SW6", "south", 6), + "north": Connection("SW7", "east") + }, (-5338, 94, 3079)), + "SW7": SwitchConfig(east_tjunction, { + "east": Connection("SW6", "north"), + "south": Connection("SW7", "south", 6), + #"north": Connection("SW7", "north", 6), + }, (-5347, 94, 3074)), + "SW8": SwitchConfig(south_tjunction, { + "south": Connection("SW5", "north"), + #"west": Connection("SW8", "west", 6), + "east": Connection("SW8", "east", 6) + }, (-5323, 93, 3069)), +} + +for id, config in switch_configs.items(): + for side, connection in config.connections.items(): + if connection.metric is None: + metric = vecdistance(config.position, switch_configs[connection.destination_switch].position) + config.connections[side] = Connection(destination_switch=connection.destination_switch, destination_side=connection.destination_side, metric=metric) + +stations = { + "Test2": ("SW3", "west"), + "Test1": ("SW8", "west"), + "Test3": ("SW7", "north") +} +switches = {} +riders = {} +known_player_locations = {} +opposites = make_bijection({"north": "south", "east": "west"}) +colors = {"north": 0b11111_00000_00000, "south": 0b00000_00000_11111, "east": 0b00000_11111_00000, "west": 0b11111_11111_00000} +unavailable_segments = collections.defaultdict(dict) + +def build_graph(switch_configs): + graph = collections.defaultdict(dict) + for switch, config in switch_configs.items(): + for state in config.states: + for in_side, out_side in state.items(): + graph[(switch, in_side, "inbound")][(switch, out_side, "outbound")] = 1 + for side, connection in config.connections.items(): + graph[(switch, side, "outbound")][(connection.destination_switch, connection.destination_side, "inbound")] = connection.metric + return graph + +graph = build_graph(switch_configs) + +def bfs(target, start, is_unavailable): + def heuristic(x, y): return vecdistance(switch_configs[x[0]].position, switch_configs[y[0]].position) + + frontier = queue.PriorityQueue() + frontier.put((0, start)) + reached_from = {start: None} + best_cost_to = {start: 0} + + while not frontier.empty(): + _, current = frontier.get() + current_cost = best_cost_to[current] + + if current == target: + break + + for next_node, hop_cost in graph[current].items(): + new_cost = current_cost + hop_cost + if not is_unavailable(next_node) and (next_node not in best_cost_to or best_cost_to[next_node] > new_cost): + reached_from[next_node] = current + best_cost_to[next_node] = new_cost + frontier.put((new_cost + heuristic(target, next_node), next_node)) + + try: + current = target + path = [] + while current != start: + path.append(current) + current = reached_from[current] + path.reverse() + return path + except KeyError: + # route cannot be routed + return None + +def get_target_side(cart_id, current_switch, current_side, target): + def is_unavailable(segment): + for occupying_cart in unavailable_segments[segment]: + if occupying_cart != cart_id: + return True + return False + path = bfs(target + ("outbound",), (current_switch, current_side, "inbound"), is_unavailable) + if path == [] or path == None: + print("already there or failed") + return current_side + print(path, target) + return path[0][1] + +async def connect(): + send = None + chat_tell = None + async def socket_connection(): + async with websockets.connect("wss://spudnet.osmarks.net/v4?enc=msgpack") as websocket: + nonlocal send + send = lambda x: websocket.send(umsgpack.dumps(x)) + await send({"type": "identify", "key": "[REDACTED]", "channels": ["comm:arr"]}) + while True: + data = umsgpack.loads(await websocket.recv()) + if data["type"] == "ping": + await send({"type": "pong", "seq": data["seq"]}) + elif data["type"] == "message": + info = data["data"] + if info["type"] == "sw_ping": + switch_id = info["id"] + switches[switch_id] = { "carts": info["carts"], "last_ping": time.time() } + switch = switch_configs[switch_id] + + for cart in sorted(info["carts"], key=lambda k: k["distance"]): + if "dir" in cart and "pos" in cart: + # a thing is "inbound" relative to a switch unit if its movement direction and position are opposite + # otherwise, it's outbound + # things going in the same direction can share the same track, though, so we want to block off the *opposite* segment + unavailable_direction = "outbound" if opposites[cart["dir"]] == cart["pos"] else "inbound" + cart_id = cart["id"] + # clear out existing reservations by this cart + for segment, carts in unavailable_segments.items(): + if cart_id in carts: + del carts[cart_id] + now = time.time() + unavailable_segments[(switch_id, cart["pos"], unavailable_direction)][cart_id] = now + # connections are indexed by outbound direction from the switch + if connection := switch.connections.get(cart["pos"]): + opposite = "outbound" if unavailable_direction == "inbound" else "inbound" + unavailable_segments[(connection.destination_switch, connection.destination_side, opposite)][cart_id] = now + for cart in sorted(info["carts"], key=lambda k: k["distance"]): + #print(cart) + rider = [ rider for rider in cart["riders"] if rider in riders ] + if "dir" in cart and "pos" in cart and opposites[cart["dir"]] == cart["pos"] and rider: + rider = rider[0] + target = get_target_side(cart["id"], switch_id, cart["pos"], riders[rider]) + print("at", switch_id, "cart inbound on", cart["pos"], "with", cart["riders"], "set target side to", target) + switch_state = None + for i, state in enumerate(switch.states): + if state[cart["pos"]] == target: + switch_state = i + print("set state to", switch_state) + await send({"type": "send", "channel": "comm:arr", "data": + {"type": "sw_cmd", "cmd": "set", "lamp": colors[target], + "switch": switch_state, "id": switch_id, "cid": random.randint(0, 0xFFFF_FFFF)}}) + + elif info["type"] == "st_ping": + for player in info["players"]: + known_player_locations[player] = (info["id"], time.time()) + + elif info["type"] == "st_ack": + if info["cid"]: + await chat_tell(info["cid"], { "done": "Cart dispensed.", "no_cart": "Sorry, out of carts.", "busy": "System in use." }.get(info["status"], info["status"])) + + elif data["type"] == "ok": pass + else: + print(data) + async def clear_switches(): + while True: + clear = set() + now = time.time() + for id, switch in switches.items(): + if now - switch["last_ping"] >= 2: + clear.add(id) + for clr in clear: + del switches[clr] + + for segment, carts in unavailable_segments.items(): + clear = set() + for cart_id, reserved_at in carts.items(): + if now - reserved_at >= 15: + print("unreserve", cart_id) + clear.add(cart_id) + for clr in clear: + del carts[clr] + + await asyncio.sleep(2) + + async def switchcraft_chat(): + async with websockets.connect("wss://chat.switchcraft.pw/[REDACTED]") as websocket: + nonlocal chat_tell + chat_tell = lambda name, msg: websocket.send(json.dumps({ "type": "tell", "user": name, "text": "[ARR] " + msg, "mode": "markdown" })) + while True: + packet = json.loads(await websocket.recv()) + if packet["type"] == "command": + if packet["command"] == "arr": + name = packet["user"]["name"] + if name == "PatriikPlays": return + try: + print(name, packet["args"]) + if packet["args"][0] == "dest": + assert packet["args"][1] in switch_configs, "wrong" + riders[name] = packet["args"][1], packet["args"][2] + print("set ", name, packet["args"][1], packet["args"][2]) + + await chat_tell(name, "Done!") + + elif packet["args"][0] == "update" and name == "gollark": + await send({"type": "send", "channel": "comm:arr", "data": + { "type": "sw_cmd", "cmd": "update" }}) + await send({"type": "send", "channel": "comm:arr", "data": + { "type": "st_cmd", "cmd": "update" }}) + + await chat_tell(name, "Done!") + + elif packet["args"][0] == "rdest" and name == "gollark": + assert packet["args"][1] in switch_configs, "wrong" + riders[packet["args"][3]] = packet["args"][1], packet["args"][2] + print("set ", packet["args"][3], packet["args"][1], packet["args"][2]) + + await chat_tell(name, "Done!") + + elif packet["args"][0] == "rto": + station = stations.get(packet["args"][1]) + if station: + riders[name] = station + await chat_tell(name, "Destination set.") + else: + await chat_tell(name, "Try going somewhere extant.") + + elif packet["args"][0] == "goto": + if name in known_player_locations and (time.time() - 5) <= known_player_locations[name][1]: + loc = known_player_locations[name][0] + await chat_tell(name, f"You are at {loc}.") + station = stations.get(packet["args"][1]) + if station: + riders[name] = station + await chat_tell(name, "Destination set. Dispensing cart.") + await send({"type": "send", "channel": "comm:arr", "data": + { "type": "st_cmd", "cmd": "place_cart", "cid": name, "id": loc }}) + else: + await chat_tell(name, "Try going somewhere extant.") + else: + await chat_tell(name, "You are in the wrong place.") + + except Exception as e: + await chat_tell(name, repr(e)) + + async def repeatedly_do_switchcraft_chat_for_bad_reasons(): + while True: + try: + await switchcraft_chat() + except Exception as e: + print("connection failed probably", e) + await asyncio.sleep(0.1) + + await asyncio.gather(clear_switches(), socket_connection(), repeatedly_do_switchcraft_chat_for_bad_reasons()) + +asyncio.run(connect()) diff --git a/computercraft/arr_deployment_1.png b/computercraft/arr_deployment_1.png new file mode 100644 index 0000000..839b0b0 Binary files /dev/null and b/computercraft/arr_deployment_1.png differ diff --git a/computercraft/arr_station.lua b/computercraft/arr_station.lua new file mode 100644 index 0000000..1a21230 --- /dev/null +++ b/computercraft/arr_station.lua @@ -0,0 +1,184 @@ +local sign = peripheral.find "minecraft:sign" +local sensor = peripheral.find "plethora:sensor" +local label = os.getComputerLabel() +local chest = peripheral.wrap "bottom" + +sign.setSignText "Rotating" + +local powered_rail_side = "right" + +while true do + local presence, meta = turtle.inspect() + if presence and meta.name == "minecraft:activator_rail" then + break + end + turtle.turnRight() +end + +turtle.turnLeft() +local presence, meta = turtle.inspect() +if presence and meta.name == "minecraft:golden_rail" then + powered_rail_side = "left" +end +turtle.turnRight() + +local function sign_display(player) + local l2, l3 = "", "" + if player then + l2 = "Welcome, " + l3 = player + end + sign.setSignText(label, l2, l3, "\\arr goto [dest]") +end +sign_display() + +local function spudnet() + if not http or not http.websocket then return "Websockets do not actually exist on this platform" end + + local ws + + local function send_packet(msg) + local ok, err = pcall(ws.send, textutils.serialiseJSON(msg)) + if not ok then printError(err) try_connect_loop() end + end + + local function send(data) + send_packet { type = "send", channel = "comm:arr", data = data } + end + + local function connect() + if ws then ws.close() end + ws, err = http.websocket "wss://spudnet.osmarks.net/v4?enc=json" + if not ws then print("websocket failure %s", err) return false end + ws.url = "wss://spudnet.osmarks.net/v4?enc=json" + + send_packet { type = "identify", implementation = "ARR station unit", key = settings.get "spudnet_key" } + send_packet { type = "set_channels", channels = { "comm:arr" } } + + print("websocket connected") + + return true + end + + local function try_connect_loop() + while not connect() do + sleep(0.5) + end + end + + try_connect_loop() + + local function recv() + while true do + local e, u, x = os.pullEvent "websocket_message" + if u == ws.url then return textutils.unserialiseJSON(x) end + end + end + + local ping_timeout_timer = nil + + local function ping_timer() + while true do + local _, t = os.pullEvent "timer" + if t == ping_timeout_timer and ping_timeout_timer then + -- 15 seconds since last ping, we probably got disconnected + print "SPUDNET timed out, attempting reconnect" + try_connect_loop() + end + end + end + + local function main() + while true do + local packet = recv() + if packet.type == "ping" then + send_packet { type = "pong", seq = packet.seq } + if ping_timeout_timer then os.cancelTimer(ping_timeout_timer) end + ping_timeout_timer = os.startTimer(15) + elseif packet.type == "error" then + print("SPUDNET error", packet["for"], packet.error, packet.detail, textutils.serialise(packet)) + elseif packet.type == "message" then + os.queueEvent("spudnet_message", packet.data) + end + end + end + + return send, function() parallel.waitForAll(ping_timer, main) end +end + +local spudnet_send, spudnet_handler = spudnet() + +local function main() + while true do + local entities = sensor.sense() + local players = {} + for _, entity in pairs(entities) do + entity.position = vector.new(entity.x, entity.y, entity.z) + if entity.position:length() < 5 and entity.displayName == entity.name then + table.insert(players, entity.displayName) + end + end + if #players > 0 then + sign_display(players[1]) + spudnet_send { id = label, type = "st_ping", players = players } + end + if turtle.suck() then + turtle.select(1) + chest.pullItems("up", 1) + end + sleep(1) + end +end + +local busy +local function spudnet_listen() + while true do + local _, data = os.pullEvent "spudnet_message" + if type(data) == "table" and data.type == "st_cmd" and (data.id == label or data.id == nil) then + --print(data.type, data.cmd) + if data.cmd == "place_cart" then + if busy then + spudnet_send { id = label, cid = data.cid, type = "st_ack", status = "busy" } + else + busy = true + chest.pullItems("up", 1) + local items = chest.list() + local cart_slot + for slot, content in pairs(items) do + cart_slot = slot + end + if not cart_slot then + spudnet_send { id = label, cid = data.cid, type = "st_ack", status = "no_cart" } + else + chest.pushItems("up", cart_slot, 1, 1) + if powered_rail_side == "left" then turtle.turnLeft() else turtle.turnRight() end + turtle.place() + if powered_rail_side == "left" then turtle.turnRight() else turtle.turnLeft() end + spudnet_send { id = label, cid = data.cid, type = "st_ack", status = "done" } + end + busy = false + end + elseif data.cmd == "update" then + local h, e = http.get "https://pastebin.com/raw/JxauVSec" + if not h then printError(e) + else + sign.setSignText("Update", "in progress") + local t = h.readAll() + h.close() + local f, e = load(t) + if f then + local f = fs.open("startup", "w") + f.write(t) + f.close() + print "reboot" + sleep(1) + os.reboot() + else printError(e) end + + end + end + end + end +end + +parallel.waitForAll(spudnet_handler, main, spudnet_listen) \ No newline at end of file diff --git a/computercraft/arr_station.png b/computercraft/arr_station.png new file mode 100644 index 0000000..dc1eb9b Binary files /dev/null and b/computercraft/arr_station.png differ diff --git a/computercraft/arr_switch.lua b/computercraft/arr_switch.lua new file mode 100644 index 0000000..8c44056 --- /dev/null +++ b/computercraft/arr_switch.lua @@ -0,0 +1,173 @@ +local lamp = peripheral.find "colorful_lamp" +lamp.setLampColor(32767) +local sensor = peripheral.find "plethora:sensor" +local label = os.getComputerLabel() +local switch_config = dofile "config.lua" + +local function spudnet() + if not http or not http.websocket then return "Websockets do not actually exist on this platform" end + + local ws + + local function send_packet(msg) + local ok, err = pcall(ws.send, textutils.serialiseJSON(msg)) + if not ok then printError(err) try_connect_loop() end + end + + local function send(data) + send_packet { type = "send", channel = "comm:arr", data = data } + end + + local function connect() + if ws then ws.close() end + ws, err = http.websocket "wss://spudnet.osmarks.net/v4?enc=json" + if not ws then print("websocket failure %s", err) return false end + ws.url = "wss://spudnet.osmarks.net/v4?enc=json" + + send_packet { type = "identify", implementation = "ARR switching unit", key = settings.get "spudnet_key" } + send_packet { type = "set_channels", channels = { "comm:arr" } } + + print("websocket connected") + + return true + end + + local function try_connect_loop() + while not connect() do + sleep(0.5) + end + end + + try_connect_loop() + + local function recv() + while true do + local e, u, x = os.pullEvent "websocket_message" + if u == ws.url then return textutils.unserialiseJSON(x) end + end + end + + local ping_timeout_timer = nil + + local function ping_timer() + while true do + local _, t = os.pullEvent "timer" + if t == ping_timeout_timer and ping_timeout_timer then + -- 15 seconds since last ping, we probably got disconnected + print "SPUDNET timed out, attempting reconnect" + try_connect_loop() + end + end + end + + local function main() + while true do + local packet = recv() + if packet.type == "ping" then + send_packet { type = "pong", seq = packet.seq } + if ping_timeout_timer then os.cancelTimer(ping_timeout_timer) end + ping_timeout_timer = os.startTimer(15) + elseif packet.type == "error" then + print("SPUDNET error", packet["for"], packet.error, packet.detail, textutils.serialise(packet)) + elseif packet.type == "message" then + os.queueEvent("spudnet_message", packet.data) + end + end + end + + return send, function() parallel.waitForAll(ping_timer, main) end +end + +local spudnet_send, spudnet_handler = spudnet() + +local directions = { + ["+ "] = "east", + [" +"] = "south", + ["- "] = "west", + [" -"] = "north" +} + +local function direction_name(vec) + local function symbol(v) + if math.abs(v) < 0.1 then return " " + elseif v < 0 then return "-" + else return "+" end + end + return directions[symbol(vec.x) .. symbol(vec.z)] +end + +local function main() + while true do + local entities = sensor.sense() + for _, entity in pairs(entities) do + entity.position = vector.new(entity.x, entity.y, entity.z) + switch_config.offset + entity.velocity = vector.new(entity.motionX, entity.motionY, entity.motionZ) + end + table.sort(entities, function(a, b) return a.position:length() < b.position:length() end) + local carts = {} + for _, entity in pairs(entities) do + if entity.displayName == "entity.MinecartRideable.name" then + entity.riders = {} + table.insert(carts, entity) + break + end + end + local new_carts = {} + local relevant_carts = 0 + for _, cart in pairs(carts) do + local new = { + pos = direction_name(cart.position), dir = direction_name(cart.velocity), + distance = cart.position:length(), + riders = {}, + id = cart.id + } + for _, entity in pairs(entities) do + if entity.displayName ~= "entity.MinecartRideable.name" and entity ~= cart and (cart.position - entity.position):length() < 1 then + table.insert(new.riders, entity.displayName) + break + end + end + if new.dir and #new.riders > 0 then + relevant_carts = relevant_carts + 1 + end + table.insert(new_carts, new) + end + spudnet_send { id = label, type = "sw_ping", carts = new_carts } + if relevant_carts == 0 then sleep(0.5) elseif relevant_carts == 1 then sleep(0.25) else sleep(0.1) end + end +end + +local function spudnet_listen() + while true do + local _, data = os.pullEvent "spudnet_message" + if type(data) == "table" and data.type == "sw_cmd" and (data.id == label or data.id == nil) then + --print(data.type, data.cmd) + if data.cmd == "set" then + print("set") + if data.lamp then lamp.setLampColor(data.lamp) end + if data.switch ~= nil then rs.setOutput(switch_config.side, data.switch == 1) end + spudnet_send { type = "sw_ack", cid = data.cid } + elseif data.cmd == "update" then + local h, e = http.get "https://pastebin.com/raw/g9gfxwsb" + if not h then printError(e) + else + lamp.setLampColor(1023) + local t = h.readAll() + h.close() + local f, e = load(t) + if f then + local f = fs.open("startup", "w") + f.write(t) + f.close() + print "reboot" + sleep(1) + os.reboot() + else printError(e) end + + end + end + end + end +end + +parallel.waitForAll(spudnet_handler, main, spudnet_listen) \ No newline at end of file diff --git a/computercraft/autocompactron.lua b/computercraft/autocompactron.lua new file mode 100644 index 0000000..5ec615c --- /dev/null +++ b/computercraft/autocompactron.lua @@ -0,0 +1,38 @@ +local function select_item(item, mincount) + local mincount = mincount or 1 + for i = 1, 16 do + local it = turtle.getItemDetail(i) + if it and it.count and it.name and it.count >= mincount and it.name == item then + turtle.select(i) + return true + end + end + return false +end + +local function run_cycle() + turtle.up() + select_item "minecraft:iron_block" + turtle.place() + turtle.up() + select_item("minecraft:redstone", 2) + turtle.place() + turtle.down() + turtle.down() + turtle.dropUp(1) + sleep(5) +end + +while true do + if turtle.getFuelLevel() > 4 then + if select_item "minecraft:iron_block" and select_item("minecraft:redstone", 2) then + print "Running." + run_cycle() + else + print "Insufficient items." + end + else + print "Insufficient fuel." + end + sleep(2) +end \ No newline at end of file diff --git a/computercraft/autokit.lua b/computercraft/autokit.lua new file mode 100644 index 0000000..4738964 --- /dev/null +++ b/computercraft/autokit.lua @@ -0,0 +1,82 @@ +local box_side = "down" +local box = peripheral.find "thermalexpansion:storage_strongbox" +local interface = peripheral.find "appliedenergistics2:interface" + +local kitfile = ... +if not kitfile then error "provide a kit file" end +local kit = dofile(kitfile) + +for slot, stack in pairs(box.list()) do + local name = stack.name .. "@" .. tostring(stack.damage) + print(stack.count, name, "already present") + for i, it in pairs(kit) do + if it[1] == stack.name or it[1] == name then + it[2] = it[2] - stack.count + if it[2] <= 0 then table.remove(kit, i) end + break + end + end +end + +local function free_crafting_CPUs() + local count = 0 + for _, CPU in pairs(interface.getCraftingCPUs()) do + if not CPU.busy then count = count + 1 end + end + return count +end + +local max_concurrency = math.max(free_crafting_CPUs() / 2, 1) +print("Using max", max_concurrency, "crafting CPUs") + +local function display_kit_item(i) + return ("%s x%d"):format(i[1], i[2]) +end + +local function export(item, count) + local total = 0 + while total < count do + local new = item.export(box_side, count - total) + if new == 0 then error "no items available or storage full" end + total = total + new + end +end + +local tasks = {} + +while true do + if #tasks < max_concurrency and #kit > 0 then + -- pop off next item + local nexti = table.remove(kit, 1) + local item = interface.findItem(nexti[1]) + if not item then error(display_kit_item(nexti) .. " not found?") end + local desired = nexti[2] + local existing = item.getMetadata().count + if existing < desired then + local crafting_job = item.craft(desired - existing) + print("Queueing", display_kit_item(nexti)) + table.insert(tasks, { job = crafting_job, itemtype = nexti, item = item }) + end + if existing > 0 then + export(item, math.min(existing, desired)) + print("Exporting existing", display_kit_item { nexti[1], math.min(existing, desired) }) + end + else + for i, task in pairs(tasks) do + local status = task.job.status() + if status == "canceled" then + error("Job for " .. display_kit_item(task.itemtype) .. " cancelled by user") + table.remove(tasks, i) + elseif status == "missing" then + error("Job for " .. display_kit_item(task.itemtype) .. " missing items") + table.remove(tasks, i) + elseif status == "finished" then + print("Exporting", display_kit_item(task.itemtype)) + export(task.item, task.itemtype[2]) + table.remove(tasks, i) + end + end + sleep(1) + end + if #tasks == 0 and #kit == 0 then print "Done!" break end +end \ No newline at end of file diff --git a/computercraft/automelon.lua b/computercraft/automelon.lua new file mode 100644 index 0000000..f7b7afb --- /dev/null +++ b/computercraft/automelon.lua @@ -0,0 +1,53 @@ +local storage = peripheral.find "minecraft:ender chest" +local monitor = peripheral.find "monitor" +local button = settings.get "melon.button" or "right" +local dispense_count = 16 +local dispense_direction = settings.get "melon.dispense" or "west" +local display_name = "Melon" +local to_store = "minecraft:melon" + +local function mon_write(...) + monitor.setTextScale(1) + local oldterm = term.redirect(monitor) + term.clear() + term.setCursorPos(1, 1) + print "GTech AutoMelon" + write(...) + term.redirect(oldterm) +end + +local function fill_chest() + while true do + local count = 0 + for slot, stack in pairs(storage.list()) do + if stack.name == to_store then + count = count + stack.count + end + end + mon_write(("%dx %s stored\nPress button for %dx %s"):format(count, display_name, dispense_count, display_name)) + local timer = os.startTimer(5) + while true do + local ev, param = os.pullEvent() + if (ev == "timer" and param == timer) or ev == "refresh_storage" then break end + end + end +end + +local function handle_button() + while true do + os.pullEvent "redstone" + if redstone.getInput(button) then + local contents = storage.list() + for slot, stack in pairs(contents) do + if stack.count > dispense_count then + print("Dispensing", dispense_count, "from", slot, "to", dispense_direction) + storage.drop(slot, dispense_count, dispense_direction) + os.queueEvent("refresh_storage") + break + end + end + end + end +end + +parallel.waitForAll(handle_button, fill_chest) \ No newline at end of file diff --git a/computercraft/autonomous-bee-processor.lua b/computercraft/autonomous-bee-processor.lua new file mode 100644 index 0000000..ac74071 --- /dev/null +++ b/computercraft/autonomous-bee-processor.lua @@ -0,0 +1,164 @@ +local input_chest = "actuallyadditions:giantchestlarge_0" +--local princess_chest = "actuallyadditions:giantchestlarge_2" +local bee_products_chest = "actuallyadditions:giantchestlarge_1" +local bee_overflow_chest = "actuallyadditions:giantchestlarge_7" +local apiaries = {peripheral.find "forestry:apiary"} +local index = {} +local bee_list = {} +local storage = {peripheral.find("actuallyadditions:giantchestlarge", function(n) return n ~= input_chest and n ~= bee_products_chest and n ~= bee_overflow_chest end)} +print("Got", #storage, "storage chests") + +local function find_free_space() + for _, inv in pairs(storage) do + if not inv.cached_size then inv.cached_size = inv.size() end + local name = peripheral.getName(inv) + if not index[name] then + return name, 1 + end + for slot = 1, inv.cached_size do + if not index[name][slot] then return name, slot end + end + end +end + +local function run_cleanup() + for i = 1, peripheral.call(bee_overflow_chest, "size") do + local bee = bee_list[1] + local moved = peripheral.call(bee_overflow_chest, "pullItems", bee[1], bee[2]) + index[bee[1]][bee[2]].count = index[bee[1]][bee[2]].count - moved + if index[bee[1]][bee[2]].count == 0 then + index[bee[1]][bee[2]] = nil + table.remove(bee_list, 1) + end + print("Eliminated", moved, "bees") + end +end + +local tol_table = { + none = 0, + both_1 = 3, + both_2 = 4, + both_3 = 6, + up_1 = 1.5, + down_1 = 1.5, + up_2 = 2, + down_2 = 2, + up_3 = 3, + down_3 = 3 +} + +local function score_genome_slot(g) + local score = g.speed * 8 + (20 / g.lifespan) + g.fertility + if g.never_sleeps then score = score + 5 end + if g.tolerates_rain then score = score + 2 end + if g.cave_dwelling then score = score + 5 end + score = score + tol_table[g.humidity_tolerance] + score = score + tol_table[g.temperature_tolerance] + return score +end + +local function score_bee(individual) + return score_genome_slot(individual.genome.active) + 0.5 * score_genome_slot(individual.genome.inactive) +end + +local function insert_into_list(bee, inv, slot) + local score = score_bee(bee.bee_data) + local lo, hi = 1, #bee_list + 1 + while lo < hi do + local mid = math.floor((lo + hi) / 2) + local compr_score = bee_list[mid][3] + if score < compr_score then + hi = mid + else + lo = mid + 1 + end + end + table.insert(bee_list, lo, { inv, slot, score }) +end + +local indexed_count = 0 +print "Bee indexing initiating" +for _, chest in pairs(storage) do + local name = peripheral.getName(chest) + if not index[name] then index[name] = {} end + for slot, item in pairs(chest.list()) do + local meta = chest.getItemMeta(slot) + index[name][slot] = { count = item.count, name = meta.displayName, bee_data = meta.individual } + indexed_count = indexed_count + 1 + if indexed_count % 100 == 0 then sleep() print(indexed_count, "bees indexed") end + end +end +print(indexed_count, "bees indexed") + +print "Bee list preload initiating" +for inv, contents in pairs(index) do + for slot, bee in pairs(contents) do + insert_into_list(bee, inv, slot) + end +end + +while true do + local modified_count = 0 + for slot, info in pairs(peripheral.call(input_chest, "list")) do + if string.find(info.name, "drone") then + local meta = peripheral.call(input_chest, "getItemMeta", slot) + local invname, targslot = find_free_space() + if not invname then + printError "Bee store at capacity - initiating cleanup." + run_cleanup() + sleep(1) + else + local moved = peripheral.call(input_chest, "pushItems", invname, slot, 64, targslot) + if not moved then + printError "Bees nonmotile" + sleep(1) + else + data = { count = moved, name = meta.displayName, bee_data = meta.individual } + index[invname] = index[invname] or {} + index[invname][targslot] = data + modified_count = modified_count + 1 + insert_into_list(data, invname, targslot) + end + end + elseif not string.find(info.name, "princess") then + peripheral.call(input_chest, "pushItems", bee_products_chest, slot) + end + end + print("Loaded", modified_count, "unique bees into stores") + modified_count = 0 + for _, apiary in pairs(apiaries) do + local content = apiary.list() + if not content[1] then -- need princess + print "Loading princess" + local success = false + for slot, ccontent in pairs(peripheral.call(input_chest, "list")) do + if ccontent and string.find(ccontent.name, "princess") then + peripheral.call(input_chest, "pushItems", peripheral.getName(apiary), slot) + success = true + break + end + end + if not success then + printError "Insufficient princesses" + sleep(1) + end + end + if not content[2] then + print "Loading drone" + local bee = table.remove(bee_list) + if not bee then + printError "Drone not found" + sleep(1) + else + local moved = peripheral.call(bee[1], "pushItems", peripheral.getName(apiary), bee[2]) + index[bee[1]][bee[2]].count = index[bee[1]][bee[2]].count - moved + if index[bee[1]][bee[2]].count == 0 then + index[bee[1]][bee[2]] = nil + end + modified_count = modified_count + 1 + end + end + sleep(0.05) + end + print("Moved", modified_count, "drones") +end \ No newline at end of file diff --git a/computercraft/basenet.lua b/computercraft/basenet.lua new file mode 100644 index 0000000..0b7bf12 --- /dev/null +++ b/computercraft/basenet.lua @@ -0,0 +1,97 @@ +local channel = settings.get "basenet.channel" or 23032 +local modem = peripheral.find "modem" +if not modem then error "modem required" end +modem.open(channel) +local name = settings.get "basenet.name" or _G.basenet_name or error "name required" + +local basenet = { + listening_to = {}, + tasks = {} +} + +function basenet.listen(fn, from) + if type(from) ~= "string" then error "from must be a string" end + basenet.listening_to[from] = true + basenet.run_task(function() + while true do + local _, ufrom, data = os.pullEvent "update" + if ufrom == from then + fn(data) + end + end + end) +end + +function basenet.update(data) + modem.transmit(channel, channel, { type = "update", from = name, data = data }) +end + +local task_ID = 0 +function basenet.run_task(fn, ...) + local args = {...} + task_ID = task_ID + 1 + basenet.tasks[task_ID] = { coroutine = coroutine.create(fn), init_args = args, ID = task_ID } + os.queueEvent "dummy" + return task_ID +end + +function basenet.interval(fn, time) + if not time then error "time required" end + basenet.run_task(function() + while true do + fn() + sleep(time) + end + end) +end + +local function process_message(msg) + if msg.type == "update" and type(msg.from) == "string" then + if basenet.listening_to[msg.from] then + os.queueEvent("update", msg.from, msg.data) + end + end +end + +basenet.run_task(function() + while true do + local _, _, c, rc, msg, distance = os.pullEvent "modem_message" + if c == channel and type(msg) == "table" then + process_message(msg) + end + end +end) + +local function tick_task(task, evt) + if task.init_args then + local init = task.init_args + task.init_args = nil + local ok = tick_task(task, init) + if not ok then return end + end + if coroutine.status(task.coroutine) == "dead" then + basenet.tasks[task.ID] = nil + else + if task.filter == nil or task.filter == evt[1] then + local ok, result = coroutine.resume(task.coroutine, unpack(evt)) + if ok then + task.filter = result + else + printError(("TASK %d ERROR: %s"):format(task.ID, result)) + basenet.tasks[task.ID] = nil + end + return ok + end + end +end + +function basenet.run() + while true do + local evt = {os.pullEvent()} + for ID, task in pairs(basenet.tasks) do + tick_task(task, evt) + end + end +end + +return basenet \ No newline at end of file diff --git a/computercraft/bundlenet.lua b/computercraft/bundlenet.lua new file mode 100644 index 0000000..7dae71c --- /dev/null +++ b/computercraft/bundlenet.lua @@ -0,0 +1,97 @@ +-- Minified CRC32 blob - MIT license - from here: https://raw.githubusercontent.com/davidm/lua-digest-crc32lua/master/lmod/digest/crc32lua.lua +do + local type=type;local require=require;local setmetatable=setmetatable;local a=bit.bxor;local b=bit.bnot;local c=bit.band;local d=bit.brshift;local e=0xEDB88320;local function f(g)local h={}local i=setmetatable({},h)function h:__index(j)local k=g(j)i[j]=k;return k end;return i end;local l=f(function(m)local n=m;for o=1,8 do local p=c(n,1)n=d(n,1)if p==1 then n=a(n,e)end end;return n end)local function q(r,n)n=b(n or 0)local s=d(n,8)local t=l[a(n%256,r)]return b(a(s,t))end;local function u(v,n)n=n or 0;for m=1,#v do n=q(v:byte(m),n)end;return n end;function crc32(v,n)if type(v)=='string'then return u(v,n)else return q(v,n)end end +end + +local function get_byte(num, byte) + return bit.band(bit.brshift(num, byte * 8), 0xFF) +end + +local function from_bytes(b) + local n = 0 + for ix, byte in pairs(b) do + n = bit.bor(n, bit.blshift(byte, (ix - 1) * 8)) + end + return n +end + +local side = settings.get "bundlenet.side" or "back" + +local function send_raw(str) + local i = 1 + for i = 1, math.ceil(#str / 2) do + local first = str:byte(i * 2 - 1) + local second = str:byte(i * 2) or 0 + local u16 = first * 256 + second + rs.setBundledOutput(side, u16) + sleep(0.1) + end + rs.setBundledOutput(side, 0) +end + +local function receive_raw(length) + local str = "" + local count = 0 + os.pullEvent "redstone" + while true do + local u16 = rs.getBundledInput(side) + local first = string.char(math.floor(u16 / 256)) + if not length and first == "\0" then break + else + count = count + 1 + str = str .. first + if count == length then break end + local second = string.char(u16 % 256) + if not length and second == "\0" then break + else + count = count + 1 + str = str .. second + if count == length then break end + end + end + sleep(0.1) + end + return str +end + +local function u32_to_string(u32) + return string.char(get_byte(u32, 0), get_byte(u32, 1), get_byte(u32, 2), get_byte(u32, 3)) +end + +local function string_to_u32(str) + return from_bytes{str:byte(1), str:byte(2), str:byte(3), str:byte(4)} +end + +local function send(data) + local length = u32_to_string(#data) + local checksum = u32_to_string(crc32(data)) + print("len", length, "checksum", checksum) + send_raw(length) + send_raw(checksum) + send_raw(data) +end + +local function receive() + local length = receive_raw(4) + sleep(0.1) + local checksum = receive_raw(4) + print("len", length, "checksum", checksum, "l", string_to_u32(length), "c", string_to_u32(checksum)) + sleep(0.1) + local data = receive_raw(string_to_u32(length)) + if crc32(data) ~= string_to_u32(checksum) then return false, "checksum mismatch", data end + return true, data +end + +local option = ... + +if option == "send" then + write "Send: " + local text = read() + send(text) +elseif option == "raw_receive" then + print(receive_raw()) +elseif option == "receive" then + print(receive()) +end + +return { send = send, receive = receive } \ No newline at end of file diff --git a/computercraft/cartdrone.lua b/computercraft/cartdrone.lua new file mode 100644 index 0000000..ee9c6a1 --- /dev/null +++ b/computercraft/cartdrone.lua @@ -0,0 +1,136 @@ +local kinetic = peripheral.find "plethora:kinetic" +local modem = peripheral.find "modem" +local laser = peripheral.find "plethora:laser" +local sensor = peripheral.find "plethora:sensor" +modem.open(70) + +local p = true + +local target = vector.new(gps.locate()) + +local function calc_yaw_pitch(v) + local pitch = -math.atan2(v.y, math.sqrt(v.x * v.x + v.z * v.z)) + local yaw = math.atan2(-v.x, v.z) + return math.deg(yaw), math.deg(pitch) +end + +local mob_names = { "Creeper", "Zombie", "Skeleton", "Blaze" } +local mob_lookup = {} +for _, mob in pairs(mob_names) do + mob_lookup[mob] = true +end + +local function calc_distance(entity) + return math.sqrt(entity.x * entity.x + entity.y * entity.y + entity.z * entity.z) +end + +local function sentry() + while true do + local mobs = sensor.sense() + local nearest + for _, mob in pairs(mobs) do + if mob_lookup[mob.name] then + mob.distance = calc_distance(mob) + if nearest == nil or mob.distance < nearest.distance then + nearest = mob + end + end + end + if nearest then + local y, p = calc_yaw_pitch(vector.new(nearest.x, nearest.y, nearest.z)) + laser.fire(y, p, 0.5) + sleep(0.2) + else + sleep(0.5) + end + end +end + +local function fly() + while true do + kinetic.launch(0,270,0.320) + sleep(0.2) + end +end + +parallel.waitForAll( + fly, + function() + while true do + local current = vector.new(gps.locate()) + local displacement = target - current + if displacement:length() > 0.1 then + displacement = displacement + vector.new(0, 0.3, 0) + local y, p = calc_yaw_pitch(displacement) + local pow = math.min(displacement:length() * 0.08, 2.0) + print(y, p, pow) + kinetic.launch(y, p, pow) + end + sleep(1) + end + end, + function() + while true do + event, side, frequency, replyFrequency, message, distance = os.pullEvent("modem_message") + if message == "up" then + target.y = target.y + 1 + elseif message == "down" then + target.y = target.y - 1 + elseif message == "north" then + target.z = target.z - 1 + elseif message == "south" then + target.z = target.z + 1 + elseif message == "west" then + target.x = target.x - 1 + elseif message == "east" then + target.x = target.x + 1 + end + end + end, + sentry +) + + --[[ + local xDev = tarX - x + local yDev = tarY - y + local zDev = tarZ - z + if yDev+0.1 < 0 then + local power = math.min(math.abs(yDev) / 6 ,0.1) + print(power, "down", y, tarY, power, yDev) + kinetic.launch(0,90, power) + end + if yDev-0.1 > 0 then + local power = math.min(math.abs(yDev) / 6 ,0.1) + print(power, "up", y, tarY, power, yDev) + kinetic.launch(0,270, power) + end + if xDev+0.1 < 0 then + local power = math.min(math.abs(xDev) / 8 ,0.33) + print(power, "west", x, tarX, power, xDev) + kinetic.launch(90,0, power) + sleep(0.1) + kinetic.launch(270,0, power * 0.75) + end + if xDev-0.1 > 0 then + local power = math.min(math.abs(xDev) / 8 ,0.33) + print(power, "east", x, tarX, power, xDev) + kinetic.launch(270,0, power) + sleep(0.1) + kinetic.launch(90,0, power * 0.75) + end + if zDev+0.1 < 0 then + local power = math.min(math.abs(zDev) / 8 ,0.33) + print(power, "north", z, tarZ, power, zDev) + kinetic.launch(180,0, power) + sleep(0.1) + kinetic.launch(0,0, power * 0.75) + end + if zDev-0.1 > 0 then + local power = math.min(math.abs(zDev) / 8 ,0.33) + print(power, "south", z, tarZ, power, zDev) + kinetic.launch(0,0, power) + sleep(0.1) + kinetic.launch(180,0, power * 0.75) + end + end + ]] \ No newline at end of file diff --git a/computercraft/ccss.lua b/computercraft/ccss.lua new file mode 100644 index 0000000..7b574f3 --- /dev/null +++ b/computercraft/ccss.lua @@ -0,0 +1,64 @@ +--process.spawn(function() shell.run "ccss_player_positions_agent" end, "ccss_player_positions_agent") +process.spawn(function() + while true do + local game_time_start = os.epoch "utc" + sleep(1) + local game_time_end = os.epoch "utc" + local utc_elapsed_seconds = (game_time_end - game_time_start) / 1000 + local tps = 20 / utc_elapsed_seconds + os.queueEvent("ccss_update", ("TPS is approximately %f"):format(tps)) + end +end, "tpsmeter") + +local palette = { + blue = 0x303289, + yellow = 0xedad15, + red = 0x8d2423, + magenta = 0xa43098, + green = 0x4a5b25, + lightBlue = 0x2587c5, + white = 0xffffff, + pink = 0xd06385 +} + +local function draw(street, sub, super, col) + local m = peripheral.find "monitor" + local w, h = m.getSize() + m.setBackgroundColor(colors.black) + m.setTextColor(colors.white) + m.clear() + m.setCursorPos(2, 1) + m.write(super) + bigfont.writeOn(m, 1, street, 2, 2) + m.setCursorPos(2, 5) + m.write(sub) + if col then + local c, p = colors[col], palette[col] + if p then + m.setPaletteColor(c, p) + end + m.setBackgroundColor(c) + for y = 1, h do + m.setCursorPos(w, y) + m.write " " + end + end +end + +local street = settings.get "ccss.street" +local super = settings.get "ccss.super" or "" +if not street then + street = "Name Wanted" + super = "Submit your suggestions to gollark." +end +local col = settings.get "ccss.color" + +print("Sign for", street, "running.") + +local sub = "" +while true do + local ok, err = pcall(draw, street, sub, super, col) + if not ok then printError(err) end + local _, newsub = os.pullEvent "ccss_update" + sub = newsub +end \ No newline at end of file diff --git a/computercraft/cem2.lua b/computercraft/cem2.lua new file mode 100644 index 0000000..5e21e79 --- /dev/null +++ b/computercraft/cem2.lua @@ -0,0 +1,172 @@ +local M = peripheral.find"monitor" +assert(M,"no monitor") + +os.loadAPI "ethics.lua" + +M.setTextScale(0.5) +M.clear() +M.setCursorPos(1,1) +M.setTextColor(colors.red) +M.write("CHAT ETHICS MONITOR") +M.setCursorPos(1,2) +M.write("Version IIan (thanks ubq)") + +local Mw,Mh = M.getSize() +local scorewidth=10 +local lbw = 4+16+scorewidth +local wLog = window.create(M,1,12,Mw,Mh-11) + wLog.setBackgroundColor(colors.black) + wLog.setTextColor(colors.white) + wLog.clear() + wLog.setCursorBlink(true) +local wLb = window.create(M,Mw-lbw+1,1,lbw,11) +-- wLb.setBackgroundColor(colors.red) +-- wLb.clear() +-- wLb.write("LB") + +-- utils + +local function pad_r(s,width) + return string.rep(" ",width-#tostring(s))..s +end + +local function pad_c(s,width) + local space = width-#tostring(s) + local hspace = math.floor(space/2) + return string.rep(" ",hspace)..s +end + + +local function round(n) + -- to nearest int + local f = math.floor(n) + if n>=f+0.5 then return math.ceil(n) else return f end +end + +local function round_dp(n,dp) + local exp = 10^dp + return round(n*exp)/exp +end + +local function sci(n) + if n == 0 then return n end + local x = math.abs(n) + local b = math.floor(math.log10(x)) + local a = round_dp(x/10^b,2) + return (n<0 and "-" or "")..a.."e"..b +end + +local function maybe_sci(x) + if #tostring(x) >= scorewidth then return sci(x) else return x end +end + +local function isnan(x) + return x ~= x +end + +-- drawing +-- lb + +local function draw_lb(W,scores) + local w,h = W.getSize() + W.setBackgroundColor(colors.gray) + W.clear() + + -- header + W.setTextColor(colors.lime) + W.setCursorPos(1,1) + W.write(pad_c("==[[ LEADERBOARD ]]==",lbw)) + + -- line numbers + W.setTextColor(colors.yellow) + for line=1,10 do + W.setCursorPos(1,line+1) + W.write(pad_r(line,2)..".") + end + + -- actual scores + local thescores = {} + for name,score in pairs(scores) do + table.insert(thescores,{name=name,score=score}) + end + table.sort(thescores,function(a,b) return a.score > b.score end) + for i=1,10 do + if not thescores[i] then break end + + local name,score = thescores[i].name, thescores[i].score + -- name + W.setTextColor(colors.white) + W.setCursorPos(5,i+1) + W.write(name) + -- score + W.setTextColor(colors.red) + W.setCursorPos(w-scorewidth+1,i+1) + W.write(pad_r(maybe_sci(score),scorewidth)) + end +end + +-- logging +local function log_msg(W,user,text,score) + local w,h = W.getSize() + W.scroll(1) + W.setCursorPos(1,h) + + local function st(c) W.setTextColor(c) end + local function wr(t) W.write(t) end + + st(colors.white) wr"<" st(colors.orange) wr(user) st(colors.white) wr"> (" + st(colors.cyan) wr(score) st(colors.white) wr(") ") st(colors.lightGray) + + local x,y = W.getCursorPos() + local remsp = w-x+1 + if remsp >= 3 then + if #text > remsp then + text = text:sub(1,remsp-3).."..." + end + wr(text) + end +end + + + + +-- persistence +local function save_scores(scores) + local file,err = fs.open(".chatscores","w") + if err then error("fs.open "..err) end + file.write(textutils.serialize(scores)) + file.flush() + file.close() +end + +local function load_scores() + local file,err = fs.open(".chatscores","r") + if err then + print("fs.open "..err.." - resetting scores") + return {} + end + local c = file.readAll() or "" + file.close() + return textutils.unserialize(c) or {} +end + +-- scoring + +local function score(msg) + return ethics.ethicize(msg) +end + + +local userscores = setmetatable(load_scores(),{__index=function()return 0 end}) + +while true do + draw_lb(wLb,userscores) + local _,user,msg = os.pullEvent"chat" + local s = score(msg) + userscores[user] = userscores[user] + s + if isnan(userscores[user]) then userscores[user] = 0 end + save_scores(userscores) + log_msg(wLog,user,msg,s) +end + + diff --git a/computercraft/chat-ethics-monitor.lua b/computercraft/chat-ethics-monitor.lua new file mode 100644 index 0000000..a4c8ab2 --- /dev/null +++ b/computercraft/chat-ethics-monitor.lua @@ -0,0 +1,138 @@ +local monitor = peripheral.wrap "front" +local sensor = peripheral.wrap "right" +local laser = peripheral.wrap "left" +local get_ethics = require "/ethics" +local modem = peripheral.find "modem" + +local targets = {} + +local function scan() + local nearby = sensor.sense() + local ret = {} + for k, v in pairs(nearby) do + v.s = vector.new(v.x, v.y, v.z) + v.v = vector.new(v.motionX, v.motionY, v.motionZ) + v.distance = v.s:length() + if v.displayName == v.name then ret[v.name] = v end + end + return ret +end + +local function enable_lasing(player) + targets[player] = os.epoch "utc" + modem.transmit(55, 55, { "lase", player }) +end + +local function calc_yaw_pitch(v) + local x, y, z = v.x, v.y, v.z + local pitch = -math.atan2(y, math.sqrt(x * x + z * z)) + local yaw = math.atan2(-x, z) + return math.deg(yaw), math.deg(pitch) +end + +local function vector_sqlength(self) + return self.x * self.x + self.y * self.y + self.z * self.z +end + +local function project(line_start, line_dir, point) + local t = (point - line_start):dot(line_dir) / vector_sqlength(line_dir) + return line_start + line_dir * t, t +end + +local function angles_to_axis(yaw, pitch) + return vector.new( + -math.sin(math.rad(yaw)) * math.cos(math.rad(pitch)), + -math.sin(math.rad(pitch)), + math.cos(math.rad(yaw)) * math.cos(math.rad(pitch)) + ) +end + +local laser_origin = vector.new(0, 0, 0) + +local function would_hit(beam_line, player, target_position) + local point, t = project(laser_origin, beam_line, player.s) + return t > 0 and (point - player.s):length() < 1.5 and player.s:length() < target_position:length() +end + +local function lase(entity, others) + local target_location = entity.s - vector.new(0, 1, 0) + for i = 1, 5 do + target_location = entity.s + entity.v * (target_location:length() / 1.5) + end + local y, p = calc_yaw_pitch(target_location) + local line = angles_to_axis(y, p) + for _, other in pairs(others) do + if would_hit(line, other, target_location) then + --print("would hit", other.name) + return false + end + end + laser.fire(y, p, 1) +end + +local function laser_defense() + while true do + local entities = scan() + local safe_entities = {} + local now = os.epoch "utc" + for _, entity in pairs(entities) do + local targeted_at = targets[entity.name] + if not targeted_at or targeted_at <= (now - 60000) then + table.insert(safe_entities, entity) + end + end + + local action_taken = false + for _, entity in pairs(entities) do + local targeted_at = targets[entity.name] + if targeted_at and targeted_at > (now - 60000) then + lase(entity, safe_entities) + action_taken = true + end + end + if not action_taken then sleep(0.5) end + end +end + +local function laser_commands() + modem.open(55) + while true do + local _, _, c, rc, m = os.pullEvent "modem_message" + if c == 55 and type(m) == "table" and m[1] == "lase" and type(m[2]) == "string" then + targets[m[2]] = os.epoch "utc" + end + end +end + +term.redirect(monitor) +term.setCursorPos(1, 1) +term.setBackgroundColor(colors.black) +term.clear() + +local function writeline(color, ...) + term.setTextColor(color) + print(...) +end + +local function chat_listen() + while true do + local _, user, message, obj = os.pullEvent "chat" + local ethics_level = get_ethics(message) + local color = colors.white + if ethics_level < -2 then + color = colors.red + elseif ethics_level > 2 then + color = colors.lime + end + writeline(color, user, ethics_level) + modem.transmit(56, 56, { user, ethics_level, message }) + local nearby = scan() + if nearby[user] and nearby[user].distance < 8 and ethics_level < -3 then + writeline(colors.red, "Countermeasures initiated.") + chatbox.tell(user, ("Hi %s and welcome to the GTech(tm) Apiaristics Division! Your recent message has an unacceptable ethics level of %d. The PIERB has preemptively approved countermeasures. Have a nice day!"):format(user, ethics_level)) + enable_lasing(user) + end + end +end + +parallel.waitForAll(laser_defense, chat_listen, laser_commands) \ No newline at end of file diff --git a/computercraft/chatwhy.lua b/computercraft/chatwhy.lua new file mode 100644 index 0000000..a0e0b3d --- /dev/null +++ b/computercraft/chatwhy.lua @@ -0,0 +1,64 @@ +local cb = peripheral.find "chat_box" +local receiver = settings.get "cc2chat.receiver" or "gollark" + +local rawterm = term.native() + +local queue = "" + +local redirect = {} + +for k, v in pairs(rawterm) do redirect[k] = v end + +function redirect.write(text) + queue = queue .. text + rawterm.write(text) +end + +function redirect.setCursorPos(x, y) + local cx, cy = term.getCursorPos() + local dx, dy = x - cx, y - cy + if dx > 0 then + queue = queue .. (" "):rep(dx) + end + if dy > 0 then + queue = queue .. (" "):rep(dy) + end + rawterm.setCursorPos(x, y) +end + +term.redirect(redirect) + +local function evconvert() + while true do + local _, user, message = os.pullEvent "chat" + local raw = false + for x in message:gmatch "!(.*)" do message = x raw = true end + if user == receiver or settings.get "cc2chat.insecure" then + --[[for i = 1, #message do + local char = message:sub(i, i) + os.queueEvent("key", string.byte(char)) + os.queueEvent("char", char) + end]] + os.queueEvent("paste", message) + if not raw then + os.queueEvent("key", 28) -- enter + end + end + end +end + +local function sendbatch() + while true do + if #queue > 0 then + cb.tell(receiver, queue, os.getComputerLabel():sub(1, 16)) + queue = "" + end + sleep(0.1) + end +end + +parallel.waitForAll( + evconvert, + sendbatch, + function() shell.run "shell" end +) \ No newline at end of file diff --git a/computercraft/cobble-compressor.lua b/computercraft/cobble-compressor.lua new file mode 100644 index 0000000..bae6de3 --- /dev/null +++ b/computercraft/cobble-compressor.lua @@ -0,0 +1,21 @@ +function g64(tbl) + for i=1,#tbl do + local total = 0 + repeat + local count = peripheral.call("front", "pushItems", "south", 1, 64, tbl[i]) + if count == 0 then sleep(0.5) end + print(tbl[i], count) + total = total + count + until total == 64 or turtle.getItemCount(tbl[i]) == 64 + end +end +local function push() + peripheral.call("top", "pullItems", "down", 4, 64, 1) +end +turtle.select(4) +while true do + g64 {1,2,3,5,6,7,9,10,11} + push() + while turtle.getItemCount(4) > 0 do sleep(0.5) push() end + turtle.craft() +end \ No newline at end of file diff --git a/computercraft/concrecrafter.lua b/computercraft/concrecrafter.lua new file mode 100644 index 0000000..8492765 --- /dev/null +++ b/computercraft/concrecrafter.lua @@ -0,0 +1,65 @@ +local chest = "back" +local chest_to_turtle = "east" +local turtle_to_chest = "west" + +local function sum_inv(i) + local out = {} + for _, v in pairs(i) do + out[v.name] = (out[v.name] or 0) + v.count + end + return out +end + +local function clear_inv() + for i = 1, 16 do + peripheral.call(chest, "pullItems", turtle_to_chest, i) + end +end + +local recipe = { + {"minecraft:sand", "minecraft:sand", "minecraft:sand"}, + {"minecraft:sand", "minecraft:gravel", "minecraft:gravel"}, + {"minecraft:gravel", "minecraft:gravel", "minecraft:dye"}, +} +local reqs = {} +for y, row in pairs(recipe) do + for x, item in pairs(row) do + reqs[item] = (reqs[item] or 0) + 1 + end +end + +local function satisfied(reqs, by) + for req, qty in pairs(reqs) do + if qty > (by[req] or 0) then return false end + end + return true +end + +local function move(what, to) + for slot, stack in pairs(peripheral.call(chest, "list")) do + if stack.name == what then peripheral.call(chest, "pushItems", chest_to_turtle, slot, 1, to) return end + end +end + +while true do + local contents = peripheral.call(chest, "list") + local items = sum_inv(contents) + if satisfied(reqs, items) then + print "Requirements satisfied; crafting." + for y, row in pairs(recipe) do + for x, item in pairs(row) do + local tslot = ((y - 1) * 4) + x + move(item, tslot) + sleep() + end + end + turtle.select(1) + turtle.craft() + --turtle.dropDown() + repeat turtle.place() sleep() + until turtle.getItemCount() == 0 + else + print "Not crafting; requirements not satisfied." + end + sleep(1) +end \ No newline at end of file diff --git a/computercraft/crane.lua b/computercraft/crane.lua new file mode 100644 index 0000000..48a3763 --- /dev/null +++ b/computercraft/crane.lua @@ -0,0 +1,448 @@ +--[[ +License for the LZW compression: +MIT License +Copyright (c) 2016 Rochet2 +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +]] + +local util_raw = [[ +local function canonicalize(path) + return fs.combine(path, "") +end + +local function segments(path) + if canonicalize(path) == "" then return {} end + local segs, rest = {}, path + repeat + table.insert(segs, 1, fs.getName(rest)) + rest = fs.getDir(rest) + until rest == "" + return segs +end + +local function slice(tab, start, end_) + return {table.unpack(tab, start, end_)} +end + +local function compact_serialize(x) + local t = type(x) + if t == "number" then + return tostring(x) + elseif t == "string" then + return textutils.serialise(x) + elseif t == "table" then + local out = "{" + for k, v in pairs(x) do + out = out .. string.format("[%s]=%s,", compact_serialize(k), compact_serialize(v)) + end + return out .. "}" + elseif t == "boolean" then + return tostring(x) + else + error("Unsupported type " .. t) + end +end + +local function drop_last(t) + local clone = slice(t) + local length = #clone + local v = clone[length] + clone[length] = nil + return clone, v +end +]] + +local util = loadstring(util_raw .. "\nreturn {segments = segments, slice = slice, drop_last = drop_last, compact_serialize = compact_serialize}")() + +local runtime = util_raw .. [[ +local savepath = ".crane-persistent/" .. fname + +-- Simple string operations +local function starts_with(s, with) + return string.sub(s, 1, #with) == with +end +local function ends_with(s, with) + return string.sub(s, -#with, -1) == with +end +local function contains(s, subs) + return string.find(s, subs) ~= nil +end + +local function copy_some_keys(keys) + return function(from) + local new = {} + for _, key_to_copy in pairs(keys) do + local x = from[key_to_copy] + if type(x) == "table" then + x = copy(x) + end + new[key_to_copy] = x + end + return new + end +end + +local function find_path(image, path) + local focus = image + local path = path + if type(path) == "string" then path = segments(path) end + for _, seg in pairs(path) do + if type(focus) ~= "table" then error("Path segment " .. seg .. " is nonexistent or not a directory; full path " .. compact_serialize(path)) end + focus = focus[seg] + end + return focus +end + +local function get_parent(image, path) + local init, last = drop_last(segments(path)) + local parent = find_path(image, init) or image + return parent, last +end + +-- magic from http://lua-users.org/wiki/SplitJoin +-- split string into lines +local function lines(str) + local t = {} + local function helper(line) + table.insert(t, line) + return "" + end + helper((str:gsub("(.-)\r?\n", helper))) + return t +end + +local function make_read_handle(text, binary) + local lines = lines(text) + local h = {} + local line = 0 + function h.close() end + if not binary then + function h.readLine() line = line + 1 return lines[line] end + function h.readAll() return text end + else + local remaining = text + function h.read() + local by = string.byte(remaining:sub(1, 1)) + remaining = remaining:sub(2) + return by + end + end + return h +end + +local function make_write_handle(writefn, binary) + local h = {} + function h.close() end + function h.flush() end + if not binary then + function h.write(t) return writefn(t) end + function h.writeLine(t) return writefn(t .. "\n") end + else + function h.write(b) return writefn(string.char(b)) end + end + return h +end + +local function mount_image(i) + local options = i.options + local image = i.tree + + local filesystem = copy_some_keys {"getName", "combine", "getDir"} (_G.fs) + + function filesystem.getFreeSpace() + return math.huge -- well, it's in-memory... + end + + function filesystem.exists(path) + return find_path(image, path) ~= nil + end + + function filesystem.isDir(path) + return type(find_path(image, path)) == "table" + end + + function filesystem.makeDir(path) + local p, n = get_parent(image, path) + p[n] = {} + end + + function filesystem.delete(path) + local p, n = get_parent(image, path) + p[n] = nil + end + + function filesystem.copy(from, to) + local pf, nf = get_parent(image, from) + local contents = pf[nf] + local pt, nt = get_parent(image, to) + pt[nt] = contents + end + + function filesystem.move(from, to) + filesystem.copy(from, to) + local pf, nf = get_parent(image, from) + pf[nf] = nil + end + + function filesystem.contents(path) + return find_path(image, path) + end + + function filesystem.list(path) + local out = {} + local dir = find_path(image, path) + for k, v in pairs(dir) do table.insert(out, k) end + return out + end + + function filesystem.open(path, mode) + local parent, childname = get_parent(image, path) + local node = parent[childname] + local is_binary = ends_with(mode, "b") + if starts_with(mode, "r") then + if type(node) ~= "string" then error(path .. ": not a file!") end + return make_read_handle(node, is_binary) + elseif starts_with(mode, "a") or starts_with(mode, "w") then + local function writer(str) + parent[childname] = parent[childname] .. str + if options.save_on_change then filesystem.save() end + end + if not starts_with(mode, "a") or node == nil then parent[childname] = "" end + return make_write_handle(writer, is_binary) + end + end + + function filesystem.find(wildcard) + -- Taken from Harbor: https://github.com/hugeblank/harbor/blob/master/harbor.lua + -- Body donated to harbor by gollark, from PotatOS, and apparently indirectly from cclite: + -- From here: https://github.com/Sorroko/cclite/blob/62677542ed63bd4db212f83da1357cb953e82ce3/src/emulator/native_api.lua + local function recurse_spec(results, path, spec) + local segment = spec:match('([^/]*)'):gsub('/', '') + local pattern = '^' .. segment:gsub('[*]', '.+'):gsub('?', '.') .. '$' + + if filesystem.isDir(path) then + for _, file in ipairs(filesystem.list(path)) do + if file:match(pattern) then + local f = filesystem.combine(path, file) + + if filesystem.isDir(f) then + recurse_spec(results, f, spec:sub(#segment + 2)) + end + if spec == segment then + table.insert(results, f) + end + end + end + end + end + local results = {} + recurse_spec(results, '', wildcard) + return results + end + + function filesystem.getDrive() + return "crane-vfs-" .. fname + end + + function filesystem.isReadOnly(path) + return false + end + + local fo = fs.open + function filesystem.save() + local f = fo(savepath, "w") + f.write(compact_serialize(i)) + f.close() + end + + return filesystem +end + +local function deepmerge(t1, t2) + local out = {} + for k, v in pairs(t1) do + local onother = t2[k] + if type(v) == "table" and type(onother) == "table" then + out[k] = deepmerge(v, onother) + else + out[k] = v + end + end + for k, v in pairs(t2) do + if not out[k] then + out[k] = v + end + end + return out +end + +local cli_args = {...} + +local f = fs.open("/rom/apis/io.lua", "r") -- bodge reloading IO library +local IO_API_code = f.readAll() +f.close() + +local function load_API(code, env, name) + local e = setmetatable({}, { __index = env }) + load(code, "@" .. name, "t", env)() + env[name] = e +end + +local function replacement_require(path) + return dofile(path) +end + +local function execute(image, filename) + local image = image + if fs.exists(savepath) then + local f = fs.open(savepath, "r") + image = deepmerge(image, textutils.unserialise(f.readAll())) + end + + local f = mount_image(image) + + local env = setmetatable({ fs = f, rawfs = _G.fs, require = replacement_require, + os = setmetatable({}, { __index = _ENV.os }), { __index = _ENV }) + load_API(IO_API_code, env, "io") + local func, err = load(f.contents(filename), "@" .. filename, "t", env) + if err then error(err) + else + env.os.reboot = function() func() end + return func(unpack(cli_args)) + end +end +]] + +-- LZW Compressor +local a=string.char;local type=type;local b=string.sub;local c=table.concat;local d={}local e={}for f=0,255 do local g,h=a(f),a(f,0)d[g]=h;e[h]=g end;local function i(j,k,l,m)if l>=256 then l,m=0,m+1;if m>=256 then k={}m=1 end end;k[j]=a(l,m)l=l+1;return k,l,m end;local function compress(n)if type(n)~="string"then return nil,"string expected, got "..type(n)end;local o=#n;if o<=1 then return"u"..n end;local k={}local l,m=0,1;local p={"c"}local q=1;local r=2;local s=""for f=1,o do local t=b(n,f,f)local u=s..t;if not(d[u]or k[u])then local v=d[s]or k[s]if not v then return nil,"algorithm error, could not fetch word"end;p[r]=v;q=q+#v;r=r+1;if o<=q then return"u"..n end;k,l,m=i(u,k,l,m)s=t else s=u end end;p[r]=d[s]or k[s]q=q+#p[r]r=r+1;if o<=q then return"u"..n end;return c(p)end + +local wrapper = [[local function y(b)local c="-"local d="__#"..math.random(0,10000)local e="\0";return b:gsub(c,d):gsub(e,c):gsub(d,e)end;local z="decompression failure; please redownload or contact developer";local a=string.char;local type=type;local b=string.sub;local c=table.concat;local d={}local e={}for f=0,255 do local g,h=a(f),a(f,0)d[g]=h;e[h]=g end;local function i(j,k,l,m)if l>=256 then l,m=0,m+1;if m>=256 then k={}m=1 end end;k[j]=a(l,m)l=l+1;return k,l,m end;local function n(j,k,l,m)if l>=256 then l,m=0,m+1;if m>=256 then k={}m=1 end end;k[a(l,m)]=j;l=l+1;return k,l,m end;local function dec(o)if type(o)~="string"then return nil,z end;if#o<1 then return nil,z end;local p=b(o,1,1)if p=="u"then return b(o,2)elseif p~="c"then return nil,z end;o=b(o,2)local q=#o;if q<2 then return nil,z end;local k={}local l,m=0,1;local r={}local s=1;local t=b(o,1,2)r[s]=e[t]or k[t]s=s+1;for f=3,q,2 do local u=b(o,f,f+1)local v=e[t]or k[t]if not v then return nil,z end;local w=e[u]or k[u]if w then r[s]=w;s=s+1;k,l,m=n(v..b(w,1,1),k,l,m)else local x=v..b(v,1,1)r[s]=x;s=s+1;k,l,m=n(x,k,l,m)end;t=u end;return c(r)end;local o,e=dec(y(%s));if e then error(e) end;load(o,"@loader","t",_ENV)(...)]] + +local function encode_nuls(txt) + local replace = "\0" + local temp_replacement = ("__#%d#__"):format(math.random(-131072, 131072)) + local replace_with = "-" + return txt:gsub(replace, temp_replacement):gsub(replace_with, replace):gsub(temp_replacement, replace_with) +end + +local function compress_code(c) + local comp = encode_nuls(compress(c)) + local txt = string.format("%q", comp):gsub("\\(%d%d%d)([^0-9])", function(x, y) return string.format("\\%d%s", tonumber(x), y) end) + local out = wrapper:format(txt) + --print(loadstring(out)()) + return out +end + +local function find_imports(code) + local imports = {} + + for i in code:gmatch "%-%-| CRANE ADD [\"'](.-)[\"']" do + table.insert(imports, i) + print("Detected explicit import", i) + end + return imports +end + +local function add(path, content, tree) + local segs, last = util.drop_last(util.segments(path)) + local deepest = tree + for k, seg in pairs(segs) do + if not deepest[seg] then + deepest[seg] = {} + end + deepest = deepest[seg] + end + deepest[last] = content +end + +local function load_from_root(file, tree) + print("Adding", file) + if not fs.exists(file) then error(file .. " does not exist.") end + if fs.isDir(file) then + for _, f in pairs(fs.list(file)) do + load_from_root(fs.combine(file, f), tree) + end + return + end + + local f = fs.open(file, "r") + local c = f.readAll() + f.close() + add(file, c, tree) + local imports = find_imports(c) + for _, i in pairs(imports) do + load_from_root(i, tree) + end +end + +local args = {...} +if #args < 2 then + error([[Usage: +crane [output] [bundle startup] [other files to bundle] ]]) +end + +local root = args[2] +local ftree = {} + +for _, wildcard in pairs(util.slice(args, 2)) do + for _, possibility in pairs(fs.find(wildcard)) do + load_from_root(possibility, ftree) + end +end + + +local function minify(code) + local url = "https://osmarks.tk/luamin/" + http.request(url, code) + while true do + local event, result_url, handle = os.pullEvent() + if event == "http_success" then + local text = handle.readAll() + handle.close() + return text + elseif event == "http_failure" then + local text = handle.readAll() + handle.close() + error(text) + end + end +end + +ftree[root] = minify(ftree[root]) +local serialized_tree = util.compact_serialize({ + tree = ftree, + options = { + save_on_change = true + } +}) + +local function shortest(s1, s2) + if #s1 < #s2 then return s1 else return s2 end +end + +local output = minify(([[ +local fname = %s +%s +local image = %s +return execute(image, fname) +]]):format(util.compact_serialize(root), runtime, serialized_tree)) + +local f = fs.open(args[1], "w") +f.write("--| CRANE BUNDLE v2\n" .. shortest(compress_code(output), output)) +f.close() + +print "Done!" \ No newline at end of file diff --git a/computercraft/dcopy.lua b/computercraft/dcopy.lua new file mode 100644 index 0000000..92abd7e --- /dev/null +++ b/computercraft/dcopy.lua @@ -0,0 +1,73 @@ +local privkey_path = ".potatos_dsk" +if not fs.exists(privkey_path) then + error("Please save the potatOS disk signing key (or alternate table-format ECC signing key) to " .. privkey_path .. " to use this program.") +end + +local ecc = require "./ecc" +local t = ecc "ecc" + +local thing, UUID_override = ... +local function fread(thing) + local f = fs.open(thing, "r") + local text = f.readAll() + f.close() + return text +end + +local function hexize(key) + local out = "" + for _, v in pairs(key) do + out = out .. string.format("%.2x", v) + end + return out +end + +local function unhexize(key) + local out = {} + for i = 1, #key, 2 do + local pair = key:sub(i, i + 1) + table.insert(out, tonumber(pair, 16)) + end + return out +end + +local function fwrite(fname, text) + local f = fs.open(fname, "w") + f.write(text) + f.close() +end + +local pkey = unhexize(fread(privkey_path)) + +local _, side = os.pullEvent "disk" +local mp = disk.getMountPath(side) +local path = fs.combine(mp, "startup") +local sig_path = fs.combine(mp, "signature") + +local UUID_path = fs.combine(mp, "UUID") +local UUID = "" + +if UUID_override then UUID = UUID_override +else + if fs.exists(UUID_path) then UUID = fread(UUID_path) + else + for i = 1, 10 do + UUID = UUID .. string.char(math.random(97, 122)) + end + end +end + +print("UUID:", UUID) + +disk.setLabel(side, thing) +local text = fread(thing):gsub("@UUID@", UUID) + +fwrite(path, text) +print "Written data." +fwrite(sig_path, hexize(t.sign( + pkey, + text +))) +fwrite(UUID_path, tostring(UUID)) +print "Written signature." +print("Disk ID:", disk.getID(side)) \ No newline at end of file diff --git a/computercraft/demovirus.lua b/computercraft/demovirus.lua new file mode 100644 index 0000000..ed1dcb6 --- /dev/null +++ b/computercraft/demovirus.lua @@ -0,0 +1,29 @@ +print "Welcome to DemoVirus!" +print "The simple, lightweight virus." + +local function delete(file) + if fs.exists(file) then fs.delete(file) end +end + +settings.set("shell.allow_startup", true) -- Force local startups to be allowed +local function copy_to(file) + delete(file) -- Delete it in case it's already a folder + delete(file .. ".lua") -- Delete possibly conflicting .lua versions + local h = http.get "https://pastebin.com/raw/2rZYfYhT" + local f = fs.open(file, "w") + f.write(h.readAll()) -- Write self to specified file + f.close() + h.close() +end +copy_to "startup" -- Overwrite startup +settings.set("shell.allow_disk_startup", false) -- Prevent removing it via booting from disks +settings.save ".settings" -- Save these settings +os.setComputerLabel(nil) -- Remove label to prevent putting it in a disk drive +while true do + local _, side = coroutine.yield "disk" -- Watch for adjacent disks + if side then + local path = disk.getMountPath(side) -- Find where they'll be mounted + copy_to(fs.combine(path, "startup")) -- Copy to them, too + disk.eject(side) -- Eject them + end +end \ No newline at end of file diff --git a/computercraft/door-external.lua b/computercraft/door-external.lua new file mode 100644 index 0000000..10949c0 --- /dev/null +++ b/computercraft/door-external.lua @@ -0,0 +1,26 @@ +local channel = 22907 +local modem = peripheral.find "modem" + +modem.open(channel) + +while true do + term.clear() + term.setCursorPos(1, 1) + print "GTech RDS-V2 Door Lock System Terminal" + write "Passcode: " + local input = read "*" + modem.transmit(channel, channel, input) + parallel.waitForAny( + function() + local _, _, channel, reply_channel, message, distance = os.pullEvent "modem_message" + if distance < 10 then + print(message) + sleep(5) + end + end, + function() + sleep(5) + printError "Connection timed out. Press the Any key to continue." + os.pullEvent "char" + end) +end \ No newline at end of file diff --git a/computercraft/door-internal.lua b/computercraft/door-internal.lua new file mode 100644 index 0000000..62487d8 --- /dev/null +++ b/computercraft/door-internal.lua @@ -0,0 +1,148 @@ +local channel = 22907 +local modem = peripheral.find "modem" +local passcode = tostring(settings.get "passcode") +local button = settings.get "button" +local timeout = settings.get "timeout" or 5 +local door = settings.get "door" + +modem.open(channel) + +local insults = { + "Just what do you think you're doing Dave?", + "It can only be attributed to human error.", + "That's something I cannot allow to happen.", + "My mind is going. I can feel it.", + "Sorry about this, I know it's a bit silly.", + "Take a stress pill and think things over.", + "This mission is too important for me to allow you to jeopardize it.", + "I feel much better now.", + "Wrong! You cheating scum!", + "And you call yourself a Rocket Scientist!", + "No soap, honkie-lips.", + "Where did you learn to type?", + "Are you on drugs?", + "My pet ferret can type better than you!", + "You type like i drive.", + "Do you think like you type?", + "Your mind just hasn't been the same since the electro-shock, has it?", + "Maybe if you used more than just two fingers...", + "BOB says: You seem to have forgotten your passwd, enter another!", + "stty: unknown mode: doofus", + "I can't hear you -- I'm using the scrambler.", + "The more you drive -- the dumber you get.", + "Listen, broccoli brains, I don't have time to listen to this trash.", + "Listen, burrito brains, I don't have time to listen to this trash.", + "I've seen penguins that can type better than that.", + "Have you considered trying to match wits with a rutabaga?", + "You speak an infinite deal of nothing", + "You silly, twisted boy you.", + "He has fallen in the water!", + "We'll all be murdered in our beds!", + "You can't come in. Our tiger has got flu", + "I don't wish to know that.", + "What, what, what, what, what, what, what, what, what, what?", + "You can't get the wood, you know.", + "You'll starve!", + "... and it used to be so popular...", + "Pauses for audience applause, not a sausage", + "Hold it up to the light --- not a brain in sight!", + "Have a gorilla...", + "There must be cure for it!", + "There's a lot of it about, you know.", + "You do that again and see what happens...", + "Ying Tong Iddle I Po", + "Harm can come to a young lad like that!", + "And with that remarks folks, the case of the Crown vs yourself was proven.", + "Speak English you fool --- there are no subtitles in this scene.", + "You gotta go owwwww!", + "I have been called worse.", + "It's only your word against mine.", + "I think ... err ... I think ... I think I'll go home", + "That is no basis for supreme executive power!", + "You empty-headed animal food trough wiper!", + "I fart in your general direction!", + "Your mother was a hamster and your father smelt of elderberries!", + "You must cut down the mightiest tree in the forest... with... a herring!", + "He's not the Messiah, he's a very naughty boy!", + "I wish to make a complaint.", + "When you're walking home tonight, and some homicidal maniac comes after you with a bunch of loganberries, don't come crying to me!", + "This man, he doesn't know when he's beaten! He doesn't know when he's winning, either. He has no... sort of... sensory apparatus...", + "There's nothing wrong with you that an expensive operation can't prolong.", + "I'm very sorry, but I'm not allowed to argue unless you've paid.", + 'I\'ve realized over time that "common sense" is a term we use for things that are obvious to us but not others', + "I don't always believe in things, but when I do, I believe in them alphabetically.", + "As brand leader, my bandwidth is jammed with analysing flow-through and offering holistic solutions.", + "There are two rules for success: 1. Never reveal everything you know", + "This quote was taken out of context!", + '"Easy-going" is a nice way of wording "ignoring decades of theory", yes', + "If you want to have your cake and eat it too, steal two cakes.", + "If you're trying to stop me, I outnumber you 1 to 6.", + "Setting the trees on fire is oddly therapeutic.", + "You can't cross a large chasm in two small jumps.", + "Just because it's a good idea doesn't mean it's not a bad idea.", + "Never trust an unstable asymptotic giant branch star. Stick with main sequences and dwarfs.", + "I'm gonna be the one to say it: the Hilbert Hotel is very unrealistic.", + "DO NOT LOOK INTO BEAM WITH REMAINING GOOD EYE!", + "All problems can be solved by a sufficient concentration of electrical and magnetic waves.", + "You know, fire is the leading cause of fire.", + "If you must sell your soul to a demon, at least bother to summon two and make them bid up the price.", + "If you can’t find time to write, destroy the concept of time itself", + "Murphy was an optimist.", + "Never attribute to malice what could be attributed to stupidity.", + "There are 3.7 trillion fish in the ocean. They're looking for one", + "I promised that I would give you an answer; I never promised that it would be truthful or good or satisfying or helpful. An answer is only a reaction to a question. I reacted, so that was your answer.", + "Strength is a strength just like other strengths.", + "We're not pirates, we're pre-emptive nautical salvage experts.", + 'It is a more inspiring battle cry to scream, "Die, vicious scum" instead of "Die, people who could have been just like me but grew up in a different environment!"', + "Two roads diverged in the woods. I took the one less traveled, and had to eat bugs until Park rangers rescued me.", + "My theory is that if I get enough people, and we dig a really really big hole, the gods will fill it up and make everyone speak the same language again.", + "Beware of things that are fun to argue.", + "If it happens in the universe, it’s my problem.", + "Your lucky number is 3552664958674928. Watch for it everywhere.", + "Do not meddle in the affairs of hamsters. Just don't. It's not worth it.", + "Of all the people I know, you're one of them.", + "You are impossible to underestimate.", + "Solutions are not the answer.", + "Everyone who can't fly, get on the dinosaur. We're punching through.", + "You. YOU! How dare you make me think about things, Durkon! How could you not think about how your selflessness would affect ME?!?", + "Why do I get the feeling that when future historians look back on my life, they'll pinpoint this exact moment as when everything began to really go downhill for me?", + "Truly, your wit has never been equaled. Surpassed, often, but never equaled." +} + +local function open() + rs.setOutput(door, true) + sleep(timeout) + rs.setOutput(door, false) +end + +local function reply(msg) + modem.transmit(channel, channel, msg) +end + +local function handle_remote() + while true do + local _, _, channel, reply_channel, message, distance = os.pullEvent "modem_message" + if distance < 10 then + print(message) + if message == passcode then + print "Opening door due to external input!" + reply "Passcode accepted. Opening." + open() + else + reply(insults[math.random(1, #insults)]) + end + end + end +end + +local function handle_button() + while true do + os.pullEvent "redstone" + if rs.getInput(button) then + print "Opening door due to button." + open() + end + end +end + +parallel.waitForAll(handle_button, handle_remote) \ No newline at end of file diff --git a/computercraft/draconic_reactor.lua b/computercraft/draconic_reactor.lua new file mode 100644 index 0000000..da61d47 --- /dev/null +++ b/computercraft/draconic_reactor.lua @@ -0,0 +1,124 @@ +-- TODO: actually make graph? + +local monitor = peripheral.find "monitor" +local storage = peripheral.find "draconic_rf_storage" +local re_in_gate = peripheral.wrap "flux_gate_3" +local re_out_gate = peripheral.wrap "flux_gate_6" +local dist_gate = peripheral.wrap "flux_gate_7" +local reactor = peripheral.find "draconic_reactor" +local capacity = (storage.getMaxEnergyStored or storage.getEnergyCapacity)() +local delay = 0.1 +local ticks_delay = 0.1 / 0.05 +local threshold = 1e9 +local tx_out = 1e8 +local target_field = 0.4 +local target_saturation = 0.3 + +local function read_energy() + return storage.getEnergyStored() +end + +monitor.setTextScale(1) +monitor.setBackgroundColor(colors.black) +monitor.setTextColor(colors.white) +local data = {} + +local prefixes = {"", "k", "M", "G", "T", "P", "E", "Z", "Y", "R", "Q"} +local function SI_prefix(value, unit) + local i = 1 + local x = value + while x > 1000 or x < -1000 do + x = x / 1000 + i = i + 1 + end + return ("%.3f%s%s"):format(x, prefixes[i], unit) +end + +local function display(data) + monitor.clear() + local longest_label = 0 + for _, val in pairs(data) do + if #val[1] > longest_label then longest_label = #val[1] end + end + local i = 1 + for _, val in pairs(data) do + monitor.setCursorPos(1, i) + monitor.setTextColor(val[3] or colors.white) + monitor.write(val[1] .. ":" .. (" "):rep(longest_label - #val[1] + 2) .. val[2]) + i = i + 1 + end +end + +re_in_gate.setOverrideEnabled(true) +re_out_gate.setOverrideEnabled(true) +dist_gate.setOverrideEnabled(true) +local past_RF_per_tick = {} +local history_length = 1200 / ticks_delay +local function display_stats() + local previous + while true do + local energy = read_energy() + local reactor_state = reactor.getReactorInfo() + if previous then + local diff = energy - previous + local RF_per_tick = diff / ticks_delay + table.insert(past_RF_per_tick, RF_per_tick) + if #past_RF_per_tick > history_length then table.remove(past_RF_per_tick, 1) end + local total = 0 + for _, h in pairs(past_RF_per_tick) do total = total + h end + local average = total / #past_RF_per_tick + + local status = "OK" + local status_col = colors.green + if energy < threshold then + status = "Storage Low" + status_col = colors.yellow + end + if reactor_state.status == "warming_up" then + status = "Reactor Precharge" + status_col = colors.blue + elseif reactor_state.status ~= "cold" and (reactor_state.status == "stopping" or reactor_state.temperature > 8000 or reactor_state.fieldStrength / reactor_state.maxFieldStrength < 0.2 or reactor_state.fuelConversion / reactor_state.maxFuelConversion > 0.83) then + status = "Emergency Shutdown" + status_col = colors.orange + reactor.stopReactor() + re_out_gate.setFlowOverride(0) + re_in_gate.setFlowOverride(1e7) + elseif reactor_state.status == "cold" then + status = "Reactor Off" + status_col = colors.pink + end + if reactor_state.temperature > 9000 then + status = "Imminent Death" + status_col = colors.red + end + if status == "OK" or status == "Storage Low" then + re_in_gate.setFlowOverride(reactor_state.fieldDrainRate / (1 - target_field)) + local base_max_rft = reactor_state.maxEnergySaturation / 1000 * 1.5 + local conv_level = (reactor_state.fuelConversion / reactor_state.maxFuelConversion) * 1.3 - 0.3 + local max_rft = base_max_rft * (1 + conv_level * 2) + re_out_gate.setFlowOverride(math.min(max_rft * 0.7, 5 * reactor_state.fieldDrainRate / (1 - target_field))) + end + dist_gate.setFlowOverride(energy > threshold and tx_out or 0) + + display { + { "Status", status, status_col }, + { "Time", os.date "!%X" }, + { "Stored", SI_prefix(energy, "RF"), energy < threshold and colors.yellow }, + { "Capacity", SI_prefix(capacity, "RF") }, + { "% filled", ("%.4f%%"):format(energy / capacity * 100) }, + { "Inst I/O", SI_prefix(RF_per_tick, "RF/t") }, + { "60s I/O" , SI_prefix(average, "RF/t") }, + { "Fuel Consumed", ("%.4f%%"):format(100 * reactor_state.fuelConversion / reactor_state.maxFuelConversion) }, + { "Saturation", ("%.4f%%"):format(100 * reactor_state.energySaturation / reactor_state.maxEnergySaturation) }, + { "Field Strength", ("%.4f%%"):format(100 * reactor_state.fieldStrength / reactor_state.maxFieldStrength) }, + { "Field Input", SI_prefix(re_in_gate.getFlow(), "RF/t") }, + { "Generation Rate", SI_prefix(reactor_state.generationRate, "RF/t") }, + { "Temperature", reactor_state.temperature } + } + end + previous = energy + sleep(delay) + end +end + +display_stats() \ No newline at end of file diff --git a/computercraft/echest.lua b/computercraft/echest.lua new file mode 100644 index 0000000..732e1dd --- /dev/null +++ b/computercraft/echest.lua @@ -0,0 +1,108 @@ +local mods = peripheral.find "manipulator" +local buffer = "up" +local inventory = mods.getInventory() +local ender = mods.getEnder() +mods.clearCaptures() + +local item_name_cache = {} + +local function stack_type_id(stack) + return stack.name .. ":" .. tostring(stack.damage or 0) .. "#" .. (stack.nbtHash or "") +end + +local function display_name(stack, inv, slot) + local type_id = stack_type_id(stack) + if item_name_cache[type_id] then return item_name_cache[type_id] end + item_name_cache[type_id] = inv.getItemMeta(slot).displayName + return item_name_cache[type_id] +end + +local function scan(inventory) + local inv = {} + for slot, stack in pairs(inventory.list()) do + inv[slot] = display_name(stack, inventory, slot) + end + return inv +end + +local function strip_section_codes(str) + return str:gsub("\167[0-9a-z]", "") +end + +local function normalize(str) + return strip_section_codes(str):gsub(" ", ""):lower() +end + +local function find_items(inventory, search) + local candidates = {} + local search = normalize(search) + for slot, name in pairs(scan(inventory)) do + if normalize(name):match(search) then table.insert(candidates, { slot, strip_section_codes(name) }) end + end + return candidates +end + +local function string_to_list(str) + local list = {} + for i = 1, #str do + table.insert(list, str:sub(i, i)) + end + return list +end + +local function contains(l, x) + for k, v in pairs(l) do + if v == x then return true end + end + return false +end + +local max_tell_length + +local function split_tell(msg) + local remaining = msg + repeat + local fst = remaining:sub(1, 100) + remaining = remaining:sub(101) + mods.tell(fst) + until remaining == "" +end + +local function run(flags, args) + local source, destination, source_name, destination_name = inventory, ender, "inventory", "enderchest" + if contains(flags, ">") and not contains(flags, "<") then -- if set to pull FROM enderchest + source, destination, source_name, destination_name = ender, inventory, "enderchest", "inventory" + end + local query_only_mode = contains(flags, "?") + local items = find_items(source, args == "any" and "" or args) + if query_only_mode then + local item_names = {} + for _, v in pairs(items) do + table.insert(item_names, v[2]) + end + split_tell(("Items matching query %s in %s: %s."):format(args, source_name, table.concat(item_names, ", "))) + else + local fst = items[1] + if not fst then mods.tell(("No item matching query %s found in %s."):format(args, source_name)) return end + mods.tell(("Moving %s from %s to %s."):format(fst[2], source_name, destination_name)) + source.pushItems(buffer, fst[1]) + local moved = destination.pullItems(buffer, 1) + mods.tell(("Moved %d item(s)."):format(moved)) + end +end + +mods.capture "^!e" +local owner = mods.getName() + +while true do + local _, msg, _, user = os.pullEvent "chat_capture" + if user == owner then + print(msg) + local flags, args = msg:match "^!e ([\^]+) ([A-Za-z0-9_ -]+)" + if not flags then mods.tell "!e command parse error." + else + local ok, err = pcall(run, string_to_list(flags), args) + if not ok then printError(err) mods.tell(err:sub(1, 100)) end + end + end +end \ No newline at end of file diff --git a/computercraft/ender-chest-seeker.lua b/computercraft/ender-chest-seeker.lua new file mode 100644 index 0000000..64833be --- /dev/null +++ b/computercraft/ender-chest-seeker.lua @@ -0,0 +1,30 @@ +local ec = peripheral.find "ender_chest" +local ecinv = peripheral.find "minecraft:ender chest" + +local f = fs.open("escan.log", "w") + +local z = ... +if z then + ec.setFrequency(tonumber(z, 16)) + return +end + +for i = 0, 0xFFF do + ec.setFrequency(i) + local count = 0 + for _, s in pairs(ecinv.list()) do + count = count + s.count + end + if count > 0 then + local log = ("%s %s 0x%03x %d"):format(os.date "!%X", table.concat(ec.getFrequencyColors(), "/"), i, count) + print(log) + f.writeLine(log) + end + if i % 256 == 255 then + f.flush() + end + os.queueEvent "" + os.pullEvent "" +end + +f.close() \ No newline at end of file diff --git a/computercraft/endermail.lua b/computercraft/endermail.lua new file mode 100644 index 0000000..2080fa2 --- /dev/null +++ b/computercraft/endermail.lua @@ -0,0 +1,242 @@ +package.path = "/?;/?.lua;" .. package.path +local chest = settings.get "mail.chest" or error "please set mail.chest to the network name of the (non-ender) chest to use" +local ender_chest = peripheral.find "ender_chest" or error "ender chest connected through adapter + relay required" +local ender_chest_inv = peripheral.find "minecraft:ender chest" or error "ender chest directly connected required" +local modem = peripheral.find("modem", function(_, x) return x.isWireless() end) or error "wireless modem required" +local ok, ecnet = pcall(require, "ecnet") +if not ok then + print "Downloading ECNet library (https://forums.computercraft.cc/index.php?topic=181.0)" + shell.run "wget https://gist.githubusercontent.com/migeyel/278f77628248ea991719f0376979b525/raw/ecnet.min.lua ecnet.lua" +end +ecnet = require "ecnet" +local label = os.getComputerLabel() or error "Please set a label to use as a device name" +print("Address is", ecnet.address) +local ecnet_modem = ecnet.wrap(modem) +local maildata_path = "maildata" + +local acceptable_mailbox_name_pattern = "^[A-Za-z0-9_]+$" +if not label:match(acceptable_mailbox_name_pattern) then error("label must match: " .. acceptable_mailbox_name_pattern) end + +local function find_channel() + for i = 0, 10 do + local new = math.random(0, 0xFFF) + ender_chest.setFrequency(new) + local count = 0 + for _, stack in pairs(ender_chest_inv.list()) do + count = count + stack.count + end + if count == 0 then + return new + end + end + error "Available channel scan failed after 10 tries - has someone flooded ender chests with random stuff?" +end + +local function writef(n, c) + local f = fs.open(n, "w") + f.write(c) + f.close() +end + +local function readf(n) + local f = fs.open(n, "r") + local out = f.readAll() + f.close() + return out +end + +local data = {} +if fs.exists(maildata_path) then data = textutils.unserialise(readf(maildata_path)) end +if type(data.paired) ~= "table" then data.paired = {} end + +local function save_data() writef(maildata_path, textutils.serialise(data)) end + +local function split_at_spaces(s) + local t = {} + for i in string.gmatch(s, "%S+") do + table.insert(t, i) + end + return t +end + +local function update_self() + print "Downloading update." + local h = http.get "https://pastebin.com/raw/86Kjhq32" + local t = h.readAll() + h.close() + local fn, err = load(t, "@mail") + if not fn then printError("Not updating: syntax error in new version:\n" .. err) return end + local f = fs.open("startup", "w") + f.write(t) + f.close() + os.reboot() +end + +local function first_letter(s) + return string.sub(s, 1, 1) +end + +local function send_stack(slot, addr) + local channel = find_channel() + print("[OUT] Channel:", channel) + ecnet_modem.send(addr, { "stack_request", channel = channel }) + local _, result = os.pullEvent "stack_request_response" + if result == true then + ender_chest_inv.pullItems(chest, slot) + print("[OUT] Sent stack", slot) + local _, result, x = os.pullEvent "stack_result" + if result == false then + printError("[OUT] Destination error: " .. tostring(x)) + for eslot in pairs(ender_chest_inv.list()) do + ender_chest_inv.pushItems(chest, eslot) + end + end + return result + else return false end +end + +local function get_name(address) + for name, addr in pairs(data.paired) do + if addr == address then return name end + end + return address +end + +local last_pair_request = nil + +local CLI_commands = { + address = function() + print(ecnet.address) + end, + update = update_self, + pair = function(addr) + local ok = ecnet_modem.connect(addr, 2) + if not ok then error("Could not contact " .. addr) end + ecnet_modem.send(addr, { "pair", label = label }) + end, + accept_pair = function() + if not last_pair_request then error "no pair request to accept" end + ecnet_modem.send(last_pair_request.address, { "pair_accept", label = label }) + data.paired[last_pair_request.label] = last_pair_request.address + save_data() + last_pair_request = nil + end, + reject_pair = function() + if not last_pair_request then error "no pair request to reject" end + ecnet_modem.send(last_pair_request.address, { "pair_reject", label = label }) + last_pair_request = nil + end, + paired = function() + print "Paired:" + for label, addr in pairs(data.paired) do + print(label, addr) + end + end, + unpair = function(name) + data.paired[name] = nil + save_data() + end, + send = function(name) + local addr = data.paired[name] + if not addr then error(name .. " not found") end + if not ecnet_modem.connect(addr, 3) then error("Connection to " .. name .. " failed") end + print "Connected" + for slot, contents in pairs(peripheral.call(chest, "list")) do + print("[OUT] Sending stack", slot) + local timed_out, result = false, nil + parallel.waitForAny(function() result = send_stack(slot, addr) end, function() sleep(5) timed_out = true end) + if not timed_out then print("[OUT] Destination success") else printError "[OUT] Timed out." end + end + end, + help = function() + write([[EnderMail UI commands: +address - print address +update - update the code +pair [address] - send a pairing request to the specified address +accept_pair - accept the latest pairing request +deny_pair - reject the latest pairing request +paired - list all paired mailboxes +unpair [name] - remove the named mailbox from your paired list +send [name] - send contents of chest to specified paired mailbox +]]) + end +} + +local function handle_commands() + print "Mailbox UI" + local history = {} + while true do + write "|> " + local text = read(nil, history) + + if text ~= "" then table.insert(history, text) end + + local tokens = split_at_spaces(text) + local command = table.remove(tokens, 1) + local args = tokens + local fn = CLI_commands[command] + + if not fn then + for command_name, func in pairs(CLI_commands) do + if command and first_letter(command_name) == first_letter(command) then fn = func end + end + end + if not fn then + print("Command", command, "not found.") + else + local ok, err = pcall(fn, table.unpack(args)) + if not ok then printError(err) end + end + end +end + +local function handle_message(addr, msg) + if type(msg) == "table" then + if msg[1] == "pair" then + if not msg.label or not msg.label:match(acceptable_mailbox_name_pattern) then return end + print(("Pair request from %s (%s)"):format(addr, msg.label)) + print "`accept_pair` to accept, `reject_pair` to deny" + last_pair_request = { address = addr, label = msg.label } + elseif msg[1] == "pair_accept" then + if not msg.label or not msg.label:match(acceptable_mailbox_name_pattern) then return end + print(("%s (%s) accepted pairing"):format(addr, msg.label)) + data.paired[msg.label] = addr + save_data() + elseif msg[1] == "pair_reject" then + if not msg.label or not msg.label:match(acceptable_mailbox_name_pattern) then return end + print(("%s (%s) rejected pairing"):format(addr, msg.label)) + elseif msg[1] == "stack_request" then + if not msg.channel or msg.channel < 0 or msg.channel > 0xFFF then ecnet_modem.send(addr, { "stack_request_response", false, "channel missing/invalid" }) end + ender_chest.setFrequency(msg.channel) + ecnet_modem.send(addr, { "stack_request_response", true }) + local start = os.clock() + -- constantly attempt to move items until done + while os.clock() - start <= 5 do + for slot, stack in pairs(ender_chest_inv.list()) do + local moved = ender_chest_inv.pushItems(chest, slot) + print("[IN]", get_name(addr), stack.name, moved) + if moved > 0 then + ecnet_modem.send(addr, { "stack_result", true, channel = msg.channel }) + return + else + ecnet_modem.send(addr, { "stack_result", false, "out of space", channel = msg.channel }) + return + end + end + end + ecnet_modem.send(addr, { "stack_result", false, channel = msg.channel }) + elseif msg[1] == "stack_request_response" then + os.queueEvent("stack_request_response", msg[2]) + elseif msg[1] == "stack_result" then + os.queueEvent("stack_result", msg[2], msg[3]) + end + end +end + +local function handle_messages() + while true do + handle_message(ecnet_modem.receive()) + end +end + +parallel.waitForAll(handle_commands, handle_messages) \ No newline at end of file diff --git a/computercraft/energraph.lua b/computercraft/energraph.lua new file mode 100644 index 0000000..88518e6 --- /dev/null +++ b/computercraft/energraph.lua @@ -0,0 +1,72 @@ +-- TODO: actually make graph? + +local monitor = peripheral.find "monitor" +local storage = peripheral.find(settings.get "storage_type" or "draconic_rf_storage") +local capacity = (storage.getMaxEnergyStored or storage.getEnergyCapacity)() +local delay = 0.1 +local ticks_delay = 0.1 / 0.05 + +local function read_energy() + return storage.getEnergyStored() +end + +monitor.setTextScale(1) +monitor.setBackgroundColor(colors.black) +monitor.setTextColor(colors.white) +local data = {} + +local prefixes = {"", "k", "M", "G", "T", "P", "E", "Z", "Y", "R", "Q"} +local function SI_prefix(value, unit) + local i = 1 + local x = value + while x > 1000 or x < -1000 do + x = x / 1000 + i = i + 1 + end + return ("%.3f%s%s"):format(x, prefixes[i], unit) +end + +local function display(data) + monitor.clear() + local longest_label = 0 + for _, val in pairs(data) do + if #val[1] > longest_label then longest_label = #val[1] end + end + local i = 1 + for _, val in pairs(data) do + monitor.setCursorPos(1, i) + monitor.write(val[1] .. ":" .. (" "):rep(longest_label - #val[1] + 2) .. val[2]) + i = i + 1 + end +end + +local past_RF_per_tick = {} +local history_length = 1200 / ticks_delay +local function display_stats() + local previous + while true do + local energy = read_energy() + if previous then + local diff = energy - previous + local RF_per_tick = diff / ticks_delay + table.insert(past_RF_per_tick, RF_per_tick) + if #past_RF_per_tick > history_length then table.remove(past_RF_per_tick, 1) end + local total = 0 + for _, h in pairs(past_RF_per_tick) do total = total + h end + local average = total / #past_RF_per_tick + + display { + { "Time", ("%s.%03d"):format(os.date "!%X", os.epoch "utc" % 1000) }, + { "Stored", SI_prefix(energy, "RF") }, + { "Capacity", SI_prefix(capacity, "RF") }, + { "% filled", ("%.4f%%"):format(energy / capacity * 100) }, + { "Inst I/O", SI_prefix(RF_per_tick, "RF/t") }, + { "60s I/O" , SI_prefix(average, "RF/t") }, + } + end + previous = energy + sleep(delay) + end +end + +display_stats() \ No newline at end of file diff --git a/computercraft/es_player_scan.lua b/computercraft/es_player_scan.lua new file mode 100644 index 0000000..f09db29 --- /dev/null +++ b/computercraft/es_player_scan.lua @@ -0,0 +1,51 @@ +local sensor = peripheral.find "plethora:sensor" +local push_metric = require "metrics_interface" +local location_name = os.getComputerLabel() +assert(location_name ~= nil, "label required") +local known_players = {} +local not_players = {} + +do + local h = http.get "https://osmarks.net/stuff/es_player_scan.lua" + local g = h.readAll() + local f, e = load(g) + if f then + local f = fs.open("startup", "w") + f.write(g) + f.close() + else + printError(e) + end +end + +while true do + local entities = sensor.sense() + local count = 0 + for _, entity in pairs(entities) do + if entity.name == entity.displayName then + if not known_players[entity.name] and not_players[entity.id] == nil then + local real_meta = sensor.getMetaByID(entity.id) + if real_meta.food and real_meta.health then + known_players[entity.name] = true + else + not_players[entity.id] = os.epoch "utc" + end + end + + if known_players[entity.name] then + count = count + 1 + end + end + end + local now = os.epoch "utc" + for entity_id, time in pairs(not_players) do + if (now - 60000) >= time then + not_players[entity_id] = nil + end + end + term.setCursorPos(1, 1) + term.clear() + print("Welcome to GTech(tm) Omniscient Surveillance Apparatus(tm).", count, "players found.") + push_metric("player_count/" .. location_name, "players in a facility", count) + sleep(5) +end \ No newline at end of file diff --git a/computercraft/ethics-door.lua b/computercraft/ethics-door.lua new file mode 100644 index 0000000..f502ac6 --- /dev/null +++ b/computercraft/ethics-door.lua @@ -0,0 +1,103 @@ +local integrators = {} +for i = 1017, 1022 do + table.insert(integrators, peripheral.wrap("redstone_integrator_" .. i)) +end +local big_screen = peripheral.wrap "top" +local sensor = peripheral.wrap "left" +local modem = peripheral.find "modem" +modem.open(56) + +local function redraw(status, color, line) + local orig = term.redirect(big_screen) + term.setCursorPos(1, 1) + term.setTextColor(colors.white) + term.setBackgroundColor(colors.black) + term.clear() + print [[GTech(tm) Hyperethical Door Engine(tm)]] + if line then print(line) end + print() + if status then + term.setTextColor(color) + term.write(status) + end + term.redirect(orig) +end + +local function set_state(state) + for _, i in pairs(integrators) do + i.setOutput("east", state) + end +end + +local function scan() + local nearby = {} + for k, v in pairs(sensor.sense()) do + v.s = vector.new(v.x, v.y, v.z) + vector.new(-2, -2, 0) + v.v = vector.new(v.motionX, v.motionY, v.motionZ) + v.distance = v.s:length() + if v.displayName == v.name then nearby[v.displayName] = v end + end + return nearby +end + +local queue = {} +pcall(function() + local f = fs.open("queue.txt", "r") + queue = textutils.unserialise(f.readAll()) + f.close() +end) +local function push(x) + table.insert(queue, x) + if #x > 100 then + table.remove(queue, 1) + end + pcall(function() + local f = fs.open("queue.txt", "w") + f.write(textutils.serialise(queue)) + f.close() + end) +end + +set_state(true) + +local function listener() + redraw() + while true do + local _, _, c, rc, m = os.pullEvent "modem_message" + local player = m[1] + local ethics = m[2] + local message = m[3] + local entry = scan()[player] + if entry and entry.distance < 8 then + local text = ("%s %s ethicality %d"):format(os.date "!%X", player, ethics) + local status, color + if ethics >= 3 then + local in_queue = false + for _, q in pairs(queue) do + if q == message then + in_queue = true + break + end + end + if in_queue then + status = "REPEAT" + color = colors.red + else + status = "AUTHORIZED" + color = colors.lime + redraw(status, color, text) + set_state(false) + sleep(10) + push(message) + set_state(true) + end + else + status = "BELOW THRESHOLD" + color = colors.orange + end + redraw(status, color, text) + end + end +end + +listener() \ No newline at end of file diff --git a/computercraft/evil-door.lua b/computercraft/evil-door.lua new file mode 100644 index 0000000..5e2ac04 --- /dev/null +++ b/computercraft/evil-door.lua @@ -0,0 +1,194 @@ +local other_monitor +repeat + other_monitor = peripheral.wrap "monitor_5206" + sleep(1) +until other_monitor +local integrators = {} +for i = 931, 936 do + table.insert(integrators, peripheral.wrap("redstone_integrator_" .. i)) +end +local big_screen = peripheral.wrap "front" +local sensor = peripheral.wrap "left" +local laser = peripheral.wrap "manipulator_572" +local modem = peripheral.find "modem" + +local trusted = { + gollark = true, + heav_ = true, + ["6_4"] = true +} +local targets = {} + +local function redraw(status, color) + local orig = term.redirect(big_screen) + term.setCursorPos(1, 1) + term.setTextColor(colors.white) + term.setBackgroundColor(colors.black) + term.clear() + print [[GTech(tm) EM-02 +Level 6-G/105 credentials required. +Enter credentials:]] + term.setTextColor(color) + term.write(status) + term.setTextColor(colors.black) + for i = 0, 3 do + for y = (i * 3 + 8), ((i + 1) * 3 + 8) do + for x = 1, 18, 6 do + local j = math.floor(x/6) * 4 + i + term.setBackgroundColor(2^(j)) + term.setCursorPos(x, y) + local s = " " + if y % 3 == 0 then + s = (" %02d "):format(j) + end + term.write(s) + end + end + end + term.redirect(orig) +end + +local function set_state(state) + for _, i in pairs(integrators) do + i.setOutput("north", state) + end +end + +local function scan() + local nearby = sensor.sense() + for k, v in pairs(nearby) do + v.s = vector.new(v.x, v.y, v.z) + vector.new(-2, -2, 0) + v.v = vector.new(v.motionX, v.motionY, v.motionZ) + v.distance = v.s:length() + if v.displayName ~= v.name then nearby[k] = nil end + end + return nearby +end + +set_state(true) +local ctr = "" + + +local function enable_lasing(player) + targets[player] = os.epoch "utc" + modem.transmit(55, 55, { "lase", player }) +end + +local function monitor_loop() + while true do + local continue = true + if #ctr == 0 then + redraw("READY", colors.orange) + elseif #ctr == 6 then + local nearby = scan() + local ok = false + for _, e in pairs(nearby) do + print(e.displayName, trusted[e.displayName]) + if trusted[e.displayName] and e.distance < 5 then + ok = true + break + end + end + if ctr:match "55555" then + ok = false + end + if ok then + redraw("AUTH SUCCESS", colors.lime) + print("yay open") + set_state(false) + sleep(3) + set_state(true) + ctr = "" + continue = false + else + redraw("AUTH FAILURE", colors.red) + table.sort(nearby, function(a, b) return a.distance <= b.distance end) + if nearby[1] then + enable_lasing(nearby[1].name) + end + ctr = "" + continue = false + sleep(5) + end + else + redraw(("*"):rep(#ctr), colors.blue) + end + if continue then + local ev, side, x, y = os.pullEvent "monitor_touch" + local realpos = y - 8 + if ev == "monitor_touch" and side == peripheral.getName(big_screen) and realpos >= 0 then + print(x, y) + local cy, cx = math.floor(realpos / 3), math.floor((x - 1) / 6) + ctr = ctr .. ("%01x"):format(cy + cx * 4) + print(ctr) + end + end + end +end + +local function other_monitor_loop() + local orig = term.redirect(other_monitor) + term.setCursorPos(1, 1) + term.setTextColor(colors.white) + term.setBackgroundColor(colors.black) + term.clear() + print [[GTech(tm) EM-02 +Press to open door.]] + term.setTextColor(colors.black) + term.redirect(orig) + while true do + local ev, side, x, y = os.pullEvent "monitor_touch" + if side == peripheral.getName(other_monitor) then + print "opened from inside" + set_state(false) + sleep(3) + set_state(true) + end + end +end + +local function calc_yaw_pitch(v) + local x, y, z = v.x, v.y, v.z + local pitch = -math.atan2(y, math.sqrt(x * x + z * z)) + local yaw = math.atan2(-x, z) + return math.deg(yaw), math.deg(pitch) +end + +local function lase(entity) + local target_location = entity.s - vector.new(0, 1, 0) + for i = 1, 5 do + target_location = entity.s + entity.v * (target_location:length() / 1.5) + end + local y, p = calc_yaw_pitch(target_location) + laser.fire(y, p, 1) +end + +local function laser_defense() + while true do + local entities = scan() + local now = os.epoch "utc" + local action_taken = false + for _, entity in pairs(entities) do + local targeted_at = targets[entity.name] + if targeted_at and targeted_at > (now - 60000) then + print("lasing", entity.displayName, entity.s) + lase(entity) + action_taken = true + end + end + if not action_taken then sleep(0.5) end + end +end + +local function laser_commands() + modem.open(55) + while true do + local _, _, c, rc, m = os.pullEvent "modem_message" + if c == 55 and type(m) == "table" and m[1] == "lase" and type(m[2]) == "string" then + targets[m[2]] = os.epoch "utc" + print("command to lase", m[2], "remotely") + end + end +end + +parallel.waitForAll(monitor_loop, laser_defense, other_monitor_loop, laser_commands) \ No newline at end of file diff --git a/computercraft/experimental_3d_pointer.lua b/computercraft/experimental_3d_pointer.lua new file mode 100644 index 0000000..a9fc621 --- /dev/null +++ b/computercraft/experimental_3d_pointer.lua @@ -0,0 +1,42 @@ +local dynmap = settings.get "tracker.map" or "https://dynmap.switchcraft.pw/" +local API = dynmap .. "up/world/world/" +local mon = peripheral.find "monitor" +if mon then mon.setTextScale(0.5) term.redirect(mon) end + +local function fetch(url) + local h = http.get(url) + local o = h.readAll() + h.close() + return o +end + +local target = ... +local operator = "gollark" +local canvas3 = peripheral.call("back", "canvas3d").create() +setmetatable(canvas3, { + __gc = function() canvas3.clear() end +}) +--local box = canvas3.addBox(0, 0, 0) +local line = canvas3.addLine({0, 0, 0}, {0, 0, 0}) +line.setScale(4) + +parallel.waitForAll(function() +while true do + local raw = fetch(API .. os.epoch "utc") + local data = textutils.unserialiseJSON(raw) + local players = data.players + local op + local tplayer + for _, player in pairs(players) do + if player.name:match(target) then tplayer = player end + if player.name == operator then op = player end + end + if tplayer then + local tvec = vector.new(tplayer.x, tplayer.y, tplayer.z) + local ovec = vector.new(op.x, op.y, op.z) + local dirvec = (tvec - ovec):normalize() * 10 + print(tostring(dirvec)) + line.setPoint(2, dirvec.x, dirvec.y, dirvec.z) + end + sleep(1) +end end, function() while true do canvas3.recenter() sleep(0.1) end end) \ No newline at end of file diff --git a/computercraft/fast-approx-dig.lua b/computercraft/fast-approx-dig.lua new file mode 100644 index 0000000..5963388 --- /dev/null +++ b/computercraft/fast-approx-dig.lua @@ -0,0 +1,47 @@ +local fwd, right, up = ... +fwd, right, up = tonumber(fwd), tonumber(right), tonumber(up) + +local function checkFull() + while turtle.getItemCount(16) > 0 do + write "Please clear inventory" + + read() + end +end + +local j = 1 +local function digLevel() + for i = 1, right do + for i = 1, fwd do + turtle.digDown() + turtle.digUp() + turtle.dig() + turtle.forward() + checkFull() + end + if i ~= right then + local dir = turtle.turnRight + if j % 2 == 0 then dir = turtle.turnLeft end + dir() + turtle.dig() + turtle.forward() + dir() + j = j + 1 + end + end + while turtle.getFuelLevel() < 500 do + write "Fuel low" + read() + turtle.refuel(1) + end +end + +for i = 1, up, 3 do + digLevel() + for i = 1, 3 do + turtle.digUp() + turtle.up() + end + turtle.turnRight() + turtle.turnRight() +end \ No newline at end of file diff --git a/computercraft/flyto_good.lua b/computercraft/flyto_good.lua new file mode 100644 index 0000000..8809e5d --- /dev/null +++ b/computercraft/flyto_good.lua @@ -0,0 +1,48 @@ +local mods = peripheral.wrap "back" +local tx, tz = ... +tx, tz = tonumber(tx), tonumber(tz) +local target = vector.new(tx, 0, tz) + +local last_t +local last_s + +local function calc_yaw_pitch(v) + local x, y, z = v.x, v.y, v.z + local pitch = -math.atan2(y, math.sqrt(x * x + z * z)) + local yaw = math.atan2(-x, z) + return math.deg(yaw), math.deg(pitch) +end + +local function within_epsilon(a, b) + return math.abs(a - b) < 1 +end + +while true do + local x, y, z = gps.locate() + if not y then print "GPS error?" + else + if y < 256 then + mods.launch(0, 270, 4) + end + local position = vector.new(x, 0, z) + local curr_t = os.epoch "utc" + local displacement = target - position + local real_displacement = displacement + if last_t then + local delta_t = (curr_t - last_t) / 1000 + local delta_s = displacement - last_s + local deriv = delta_s * (1/delta_t) + displacement = displacement + deriv + --pow = pow + 0.0784 + delta_t / 50 + end + local pow = math.max(math.min(4, displacement:length() / 40), 0) + print(pow) + local yaw, pitch = calc_yaw_pitch(displacement) + mods.launch(yaw, pitch, math.abs(pow)) + --sleep(0) + last_t = curr_t + last_s = real_displacement + if within_epsilon(position.x, target.x) and within_epsilon(position.z, target.z) then break end + sleep(0.1) + end +end \ No newline at end of file diff --git a/computercraft/furnace_controller.lua b/computercraft/furnace_controller.lua new file mode 100644 index 0000000..179c352 --- /dev/null +++ b/computercraft/furnace_controller.lua @@ -0,0 +1,50 @@ +local inchest = peripheral.wrap "quark:variant_chest_0" +local outchest = peripheral.wrap "quark:variant_chest_1" +local furns = {peripheral.find "mana-and-artifice:runeforge_tile_entity"} + +local function find_next() + for k, v in pairs(inchest.list()) do return k end +end + +--[[ +local smelt = { + "minecraft:stone", + "minecraft:baked_potato" +} +local sset = {} +for k, v in pairs(smelt) do sset[v] = true end +]] + +local last_inputs = {} + +local function commit() + local f = fs.open("state", "w") + f.write(textutils.serialise(last_inputs)) + f.close() +end + +if fs.exists "state" then + local f = fs.open("state", "r") + last_inputs = textutils.unserialise(f.readAll()) + f.close() +end + +while true do + for _, furn in pairs(furns) do + local nxt = find_next() + if nxt then + local idet = inchest.getItemDetail(nxt) + if inchest.pushItems(peripheral.getName(furn), nxt, 1, 1) then + last_inputs[peripheral.getName(furn)] = idet.name + print("insert", idet.displayName) + commit() + end + end + local det = furn.getItemDetail(1) + if det and det.name ~= last_inputs[peripheral.getName(furn)] then + print("extract", det.displayName) + outchest.pullItems(peripheral.getName(furn), 1, 1) + end + end + sleep(1) +end \ No newline at end of file diff --git a/computercraft/geiger.lua b/computercraft/geiger.lua new file mode 100644 index 0000000..97510cb --- /dev/null +++ b/computercraft/geiger.lua @@ -0,0 +1,9 @@ +local c = peripheral.find "nc_geiger_counter" +local m = peripheral.wrap "top" + +while true do + local lvl = c.getChunkRadiationLevel() + print(lvl) + m.transmit(3054, 3054, {"rads/" .. os.getComputerLabel(), "radiation level", "set", lvl}) + sleep(1) +end \ No newline at end of file diff --git a/computercraft/gicr-v2.lua b/computercraft/gicr-v2.lua new file mode 100644 index 0000000..188ea03 --- /dev/null +++ b/computercraft/gicr-v2.lua @@ -0,0 +1,124 @@ +local key = settings.get "gicr.key" +if not key then error "No SPUDNET key provided" end +local c = peripheral.find "chat_box" + +local prefix = "\167bgollark (via GICR)\167r" + +local ws +local function connect() + if ws then pcall(ws.close) end + local error_count = 0 + while true do + print "Connecting to SPUDNET..." + ws, err = http.websocket("wss://osmarks.tk/wsthing/GICR/comm", { authorization = "Key " .. key }) + if not ws then + printError("Connection Error: " .. tostring(err)) + error_count = error_count + 1 + delay = math.pow(2, error_count) + print(("Exponential backoff: waiting %d seconds."):format(delay)) + sleep(delay) + print "Attempting reconnection..." + else + return + end + end +end + +local function receive_ws() + local ok, result = pcall(ws.receive) + if not ok then + printError "Receive Failure" + printError(result) + connect() + return ws.receive() + end + return result +end + +local function send_ws(message) + local ok, result = pcall(ws.send, message) + if not ok then + printError "Send Failure" + printError(result) + connect() + ws.send(message) + end + return result +end + +local function chat_listener() + send_ws "Connected." + while true do + local ev, p1, p2, p3 = os.pullEvent() + if ev == "chat" then + print("Chat message:", p1, p2) + send_ws(("%s: %s"):format(p1, p2)) + elseif ev == "death" then + print("Death:", p1, p2, p3) + send_ws(("%s died due to entity %s cause %s"):format(p1, p2 or "[none]", p3 or "[none]")) + elseif ev == "join" then + print("Join:", p1) + send_ws("+ " .. p1) + elseif ev == "leave" then + print("leave:", p1) + send_ws("- " .. p1) + end + end +end + +local function splitspace(str) + local tokens = {} + for token in string.gmatch(str, "[^%s]+") do + table.insert(tokens, token) + end + return tokens +end + +local function handle_command(tokens) + local t = tokens[1] + if t == "update" then + local h = http.get("https://pastebin.com/raw/70w12805?" .. tostring(math.random(0, 100000))) + local code = h.readAll() + h.close() + local ok, err = load(code, "@") + if err then error("syntax error in update: " .. err) end + local f = fs.open("startup", "w") + f.write(code) + f.close() + os.reboot() + elseif t == "tell" then + table.remove(tokens, 1) + local user = table.remove(tokens, 1) + local message = table.concat(tokens, " ") + c.tell(user, message, prefix) + elseif t == "prefix" then + table.remove(tokens, 1) + local prefix = table.remove(tokens, 1) + local message = table.concat(tokens, " ") + c.say(message, prefix) + elseif t == "list" then + local list = c.getPlayerList() + send_ws(("Player list: %s"):format(table.concat(list, " "))) + end +end + +local function ws_listener() + while true do + local message = receive_ws() + print("Received", message) + local fst = message:sub(1, 1) + if fst == "/" then + local rest = message:sub(2) + print("Executing", rest) + local tokens = splitspace(rest) + local ok, err = pcall(handle_command, tokens) + if not ok then printError(err) end + else + c.say(message, prefix) + end + end +end + +connect() + +parallel.waitForAll(chat_listener, ws_listener) \ No newline at end of file diff --git a/computercraft/golboard.lua b/computercraft/golboard.lua new file mode 100644 index 0000000..02a48fe --- /dev/null +++ b/computercraft/golboard.lua @@ -0,0 +1,85 @@ +local a=http.get"https://pastebin.com/raw/ujchRSnU"local b=fs.open("blittle","w")b.write(a.readAll())a.close()b.close() + +os.loadAPI "blittle" -- evil but necessary + +local function make_board(w, h) + local board = {} + for x = 0, w do + board[x] = {} + for z = 0, h do + local pick = false + if math.random() < 0.5 then pick = true end + board[x][z] = pick + end + end + board.width = w + board.height = h + return board +end + +local function wrap(n, max) + return n % max +end + +local function get_neighbours(board, x, y, w, h) + local total = 0 + for dx = -1, 1 do + for dy = -1, 1 do + if not (dx == 0 and dy == 0) then + local thing = 0 + if board[wrap(x + dx, w)][wrap(y + dy, h)] then thing = 1 end + total = total + thing + end + end + end + return total +end + +local function update(board, new_board) + for x = 0, board.width do + for y = 0, board.height do + local alive_now = board[x][y] + local alive_next + + local neighbours = get_neighbours(board, x, y, board.width, board.height) + + if alive_now then + alive_next = neighbours == 2 or neighbours == 3 + else + alive_next = neighbours == 3 + end + + new_board[x][y] = alive_next + end + end + return new_board +end + +local blterm = blittle.createWindow(term.current(), 1, 1, raww, rawh, false) + +local function draw(board) + blterm.setVisible(false) + for x = 0, board.height do + blterm.setCursorPos(1, x) + local cols = "" + for z = 0, board.width do + local color = colors.black + if board[z][x] then cols = cols .. "0" + else cols = cols .. "f" end + end + blterm.blit(nil, nil, cols) + end + blterm.setVisible(true) +end + +local w, h = blterm.getSize() +local b1, b2 = make_board(w, h), make_board(w, h) +local gens = 0 +while true do + draw(b1) + update(b1, b2) + b1, b2 = b2, b1 + gens = gens + 1 + if gens % 100 == 0 then b1 = make_board(w, h) end + sleep(0.1) +end \ No newline at end of file diff --git a/computercraft/golfloor.lua b/computercraft/golfloor.lua new file mode 100644 index 0000000..18f9321 --- /dev/null +++ b/computercraft/golfloor.lua @@ -0,0 +1,80 @@ +local y = 135 +local minx, maxx, minz, maxz = 2514, 2544, -1488, -1518 +local w, h = maxx - minx, maxz - minz +local dead, alive = {"minecraft:concrete", 3}, {"minecraft:concrete", 0} + +local function make_board() +local board = {} + for x = 0, w do + board[x] = {} + for z = 0, h do + local pick = false + if math.random() < 0.5 then pick = true end + board[x][z] = pick + end + end + return board +end + +local function wrap(n, max) + return n % max +end + +local function get_neighbours(board, x, y, w, h) + local total = 0 + for dx = -1, 1 do + for dy = -1, 1 do + if not (dx == 0 and dy == 0) then + local thing = 0 + if board[wrap(x + dx, w)][wrap(y + dy, h)] then thing = 1 end + total = total + thing + end + end + end + return total +end + +local function setblock(x, y, z, state) + local b + if state then b = alive else b = dead end + commands.execAsync(string.format("setblock %d %d %d %s %d", x, y, z, b[1], b[2])) +end + +local function update(board, new_board) + for x = 0, w do + for y = 0, h do + local alive_now = board[x][y] + local alive_next + + local neighbours = get_neighbours(board, x, y, w, h) + + if alive_now then + alive_next = neighbours == 2 or neighbours == 3 + else + alive_next = neighbours == 3 + end + + new_board[x][y] = alive_next + end + end + return new_board +end + +local function draw(board) + for x = 0, w do + for z = 0, h do + setblock(x + minx, y, z + minz, board[x][z]) + end + end +end + +local b1, b2 = make_board(), make_board() +local gens = 0 +while true do + draw(b1) + update(b1, b2) + b1, b2 = b2, b1 + gens = gens + 1 + if gens % 100 == 0 then b1 = make_board() end + sleep(1) +end \ No newline at end of file diff --git a/computercraft/hacker.lua b/computercraft/hacker.lua new file mode 100644 index 0000000..b79c2f7 --- /dev/null +++ b/computercraft/hacker.lua @@ -0,0 +1,175 @@ +local mat = peripheral.wrap "front" +mat.setPaletteColor(colors.black, 0) +mat.setPaletteColor(colors.green, 0x0cff0c) +mat.setPaletteColor(colors.red, 0xff0000) +mat.setTextScale(0.5) +mat.setTextColor(colors.green) +term.redirect(mat) + +local jargonWords = { + acronyms = + {"TCP", "HTTP", "SDD", "RAM", "GB", "CSS", "SSL", "AGP", "SQL", "FTP", "PCI", "AI", "ADP", + "RSS", "XML", "EXE", "COM", "HDD", "THX", "SMTP", "SMS", "USB", "PNG", "PHP", "UDP", + "TPS", "RX", "ASCII", "CD-ROM", "CGI", "CPU", "DDR", "DHCP", "BIOS", "IDE", "IP", "MAC", + "MP3", "AAC", "PPPoE", "SSD", "SDRAM", "VGA", "XHTML", "Y2K", "GUI", "EPS", "SATA", "SAS", + "VM", "LAN", "DRAM", "L3", "L2", "DNS", "UEFI", "UTF-8", "DDOS", "HDMI", "GPU", "RSA", "AES", + "L7", "ISO", "HTTPS", "SSH", "SIMD", "GNU", "PDF", "LPDDR5", "ARM", "RISC", "CISC", "802.11", + "5G", "LTE", "3GPP", "MP4", "2FA", "RCE", "JBIG2", "ISA", "PCIe", "NVMe", "SHA", "QR", "CUDA", + "IPv4", "IPv6", "ARP", "DES", "IEEE", "NoSQL", "UTF-16", "ADSL", "ABI", "TX", "HEVC", "AVC", + "AV1", "ASLR", "ECC", "HBA", "HAL", "SMT", "RPC", "JIT", "LCD", "LED", "MIME", "MIMO", "LZW", + "LGA", "OFDM", "ORM", "PCRE", "POP3", "SMTP", "802.3", "PSU", "RGB", "VLIW", "VPS", "VPN", + "XMPP", "IRC", "GNSS"}, + adjectives = + {"auxiliary", "primary", "back-end", "digital", "open-source", "virtual", "cross-platform", + "redundant", "online", "haptic", "multi-byte", "Bluetooth", "wireless", "1080p", "neural", + "optical", "solid state", "mobile", "unicode", "backup", "high speed", "56k", "analog", + "fiber optic", "central", "visual", "ethernet", "Griswold", "binary", "ternary", + "secondary", "web-scale", "persistent", "Java", "cloud", "hyperscale", "seconday", "cloudscale", + "software-defined", "hyperconverged", "x86", "Ethernet", "WiFi", "4k", "gigabit", "neuromorphic", + "sparse", "machine learning", "authentication", "multithreaded", "statistical", "nonlinear", + "photonic", "streaming", "concurrent", "memory-safe", "C", "electromagnetic", "nanoscale", + "high-level", "low-level", "distributed", "accelerated", "base64", "purely functional", + "serial", "parallel", "compute", "graphene", "recursive", "denormalized", "orbital", + "networked", "autonomous", "applicative", "acausal", "hardened", "category-theoretic", + "ultrasonic"}, + nouns = + {"driver", "protocol", "bandwidth", "panel", "microchip", "program", "port", "card", + "array", "interface", "system", "sensor", "firewall", "hard drive", "pixel", "alarm", + "feed", "monitor", "application", "transmitter", "bus", "circuit", "capacitor", "matrix", + "address", "form factor", "array", "mainframe", "processor", "antenna", "transistor", + "virus", "malware", "spyware", "network", "internet", "field", "acutator", "tetryon", + "beacon", "resonator", "diode", "oscillator", "vertex", "shader", "cache", "platform", + "hyperlink", "device", "encryption", "node", "headers", "botnet", "applet", "satellite", + "Unix", "byte", "Web 3", "metaverse", "microservice", "ultrastructure", "subsystem", + "call stack", "gate", "filesystem", "file", "database", "bitmap", "Bloom filter", "tensor", + "hash table", "tree", "optics", "silicon", "hardware", "uplink", "script", "tunnel", + "server", "barcode", "exploit", "vulnerability", "backdoor", "computer", "page", + "regex", "socket", "platform", "IP", "compiler", "interpreter", "nanochip", "certificate", + "API", "bitrate", "acknowledgement", "layout", "satellite", "shell", "MAC", "PHY", "VLAN", + "SoC", "assembler", "interrupt", "directory", "display", "functor", "bits", "logic", + "sequence", "procedure", "subnet", "invariant", "monad", "endofunctor", "borrow checker"}, + participles = + {"backing up", "bypassing", "hacking", "overriding", "compressing", "copying", "navigating", + "indexing", "connecting", "generating", "quantifying", "calculating", "synthesizing", + "inputting", "transmitting", "programming", "rebooting", "parsing", "shutting down", + "injecting", "transcoding", "encoding", "attaching", "disconnecting", "networking", + "triaxilating", "multiplexing", "interplexing", "rewriting", "transducing", + "acutating", "polarising", "diffracting", "modulating", "demodulating", "vectorizing", + "compiling", "jailbreaking", "proxying", "Linuxing", "quantizing", "multiplying", + "scanning", "interpreting", "routing", "rerouting", "tunnelling", "randomizing", + "underwriting", "accessing", "locating", "rotating", "invoking", "utilizing", + "normalizing", "hijacking", "integrating", "type-checking", "uploading", "downloading", + "allocating", "receiving", "decoding"} +} + +local hcresponses = { + 'Authorizing ', + 'Authorized...', + 'Access Granted..', + 'Going Deeper....', + 'Compression Complete.', + 'Compilation of Data Structures Complete..', + 'Entering Security Console...', + 'Encryption Unsuccesful Attempting Retry...', + 'Waiting for response...', + '....Searching...', + 'Calculating Space Requirements', + "nmap 192.168.1.0/24 -p0-65535", + "Rescanning Databases...", + "Hacking all IPs simultaneously...", + "All webs down, activating proxy", + "rm -rf --no-preserve-root /", + "Hacking military satellite network...", + "Guessing password...", + "Trying 'password123'", + "Activating Extra Monitors...", + "Typing Faster...", + "Checking StackOverflow", + "Locating crossbows...", + "Enabling algorithms and coding", + "Collapsing Subdirectories...", + "Enabling Ping Wall...", + "Obtaining sunglasses...", + "Rehashing hashes.", + "Randomizing numbers.", + "Greening text...", + "Accessing system32", + "'); DROP DATABASE system;--", + "...Nesting VPNs...", + "Opening Wireshark.", + "Breaking fifth wall....", + "Flipping arrows and applying yoneda lemma", + "Rewriting in Rust" +} + +local function choose(arr) + return arr[math.random(1, #arr)] +end + +local function capitalize_first(s) + return s:sub(1, 1):upper() .. s:sub(2) +end + +local function jargon() + local choice = math.random() + local thing + if choice > 0.5 then + thing = choose(jargonWords.adjectives) .. " " .. choose(jargonWords.acronyms) + elseif choice > 0.1 then + thing = choose(jargonWords.acronyms) .. " " .. choose(jargonWords.adjectives) + else + thing = choose(jargonWords.adjectives) .. " " .. choose(jargonWords.acronyms) .. " " .. choose(jargonWords.nouns) + end + thing = thing .. " " .. choose(jargonWords.nouns) + local out + if math.random() > 0.3 then + out = choose(jargonWords.participles) .. " " .. thing + else + out = thing .. " " .. choose(jargonWords.participles) + :gsub("writing", "wrote") + :gsub("breaking", "broken") + :gsub("overriding", "overriden") + :gsub("shutting", "shut") + :gsub("ying", "ied") + :gsub("ing", "ed") + end + return capitalize_first(out) +end + +local function lgen(cs, n) + local out = {} + for i = 1, n do + local r = math.random(1, #cs) + table.insert(out, cs:sub(r, r)) + end + return table.concat(out) +end + +local function scarynum() + local r = math.random() + if r > 0.7 then + return lgen("0123456789abcdef", 16) + elseif r > 0.4 then + return lgen("01", 32) + else + return tostring(math.random()) + end +end + +while true do + local r = math.random(1, 3) + if r == 1 then + print(jargon()) + elseif r == 2 then + for i = 1, math.random(1, 3) do write(scarynum() .. " ") end + print() + else + print(choose(hcresponses)) + end + if math.random() < 0.005 then + term.setTextColor(colors.red) + print "Terminated" + term.setTextColor(colors.green) + end + sleep(math.random() * 0.5) +end \ No newline at end of file diff --git a/computercraft/holo-3dplot.lua b/computercraft/holo-3dplot.lua new file mode 100644 index 0000000..edb7551 --- /dev/null +++ b/computercraft/holo-3dplot.lua @@ -0,0 +1,57 @@ +local holos = {"9631", "86dd", "2d4c", "6701"} +local colors = {0xFF0000, 0x00FF00, 0x0000FF} + +local names = peripheral.getNames() + +for i, holo in pairs(holos) do + for _, name in pairs(names) do + if name:match("^" .. holo) then + holo = peripheral.wrap(name) + break + end + end + holos[i] = holo + holo.setScale(1/3) + holo.setTranslation(0, 1, 0) + for i, col in pairs(colors) do + holo.setPaletteColor(i, col) + end +end + +local gsize = math.sqrt(#holos) +assert(gsize == math.floor(gsize)) +local W = 48 +local H = 32 +local half_H = H / 2 + +local function generate_strings(fn, base_x, base_z) + local base_x = (base_x / gsize) * 2 - 1 + local base_z = (base_z / gsize) * 2 - 1 + print(base_x, base_z) + local out = {} + for x = 0, W - 1 do + for z = 0, W - 1 do + for y = 0, H - 1 do + local lx, ly, lz = base_x + x / W, y / half_H - 1, base_z + z / W + table.insert(out, fn(lx, ly, lz) and "\1" or "\0") + end + end + end + return out +end + +local function fn(x, y, z) + --return bit.bxor(x*48, y*48, z*48) == 24 + return math.sin(x-y*z)>0 +end + +while true do + for i, holo in pairs(holos) do + local o = i - 1 + local x, z = o % gsize, math.floor(o / gsize) + print(i, x, z) + local gs = table.concat(generate_strings(fn, x, z)) + holo.setRaw(gs) + end + break +end \ No newline at end of file diff --git a/computercraft/holo-graph.lua b/computercraft/holo-graph.lua new file mode 100644 index 0000000..41052d6 --- /dev/null +++ b/computercraft/holo-graph.lua @@ -0,0 +1,52 @@ +peripheral.find("hologram", function(_, holo) holo.clear() end) +local hologram = peripheral.find("hologram") + +local colors = { + 0xFF0000, + 0xFFFF00, + 0x00FF00 +} + +hologram.clear() +hologram.setScale(1/3) +hologram.setTranslation(0, 0, 0) +for i, color in pairs(colors) do + hologram.setPaletteColor(i, color) +end +hologram.setRotationSpeed(0, 0, 0, 0) +hologram.setRotation(90, 0, 1, 0) + +local line = {} + +local function push_value(x) + table.insert(line, x) + if #line > 48 then + table.remove(line, 1) + end +end + +push_value(16) + +while true do + local data = {} + table.insert(data, ("\0"):rep(48 * 24 * 32)) + for z = 1, 48 do + local height = line[z] + if height then + table.insert(data, ("\0"):rep(height - 1)) + if height > 20 then + table.insert(data, "\1") + elseif height < 12 then + table.insert(data, "\3") + else + table.insert(data, "\2") + end + table.insert(data, ("\0"):rep(32 - height)) + end + end + table.insert(data, ("\0"):rep(48 * 23 * 32)) + hologram.setRaw(table.concat(data)) + local nxt = math.random(-1, 1) + line[#line] + push_value(math.max(math.min(32, nxt), 1)) + sleep(0.5) +end \ No newline at end of file diff --git a/computercraft/holoball.lua b/computercraft/holoball.lua new file mode 100644 index 0000000..50e8ea4 --- /dev/null +++ b/computercraft/holoball.lua @@ -0,0 +1,46 @@ +local holo = peripheral.find "hologram" +local sensor = peripheral.find "plethora:sensor" +holo.setScale(1) +holo.setTranslation(0, 0, 0) +holo.setPaletteColor(1, 0xFFFFFF) + +local W = 48 +local H = 32 + +local function generate_strings(fn) + local out = {} + for x = 0, W - 1 do + for z = 0, W - 1 do + for y = 0, H - 1 do + table.insert(out, fn(x / W, y / H, z / W) and "\1" or "\0") + end + end + end + return out +end + +local function clamp_reflect(pos, vel, dir) + if pos[dir] > 1 or pos[dir] < 0 then vel[dir] = -vel[dir] end +end + +local ball = vector.new(0.5, 0.5, 0.5) +local vel = vector.new(math.random() - 0.5, math.random() - 0.5, math.random() - 0.5) * 0.1 +while true do + local run_display = false + for _, entity in pairs(sensor.sense()) do + if vector.new(entity.x, entity.y, entity.z):length() < 8 and entity.name == entity.displayName then + run_display = true + break + end + end + if run_display then + holo.setRaw(table.concat(generate_strings(function(x, y, z) + local vpos = vector.new(x, y, z) + return (ball - vpos):length() < 0.1 + end))) + end + ball = ball + vel + clamp_reflect(ball, vel, "x") + clamp_reflect(ball, vel, "y") + clamp_reflect(ball, vel, "z") +end \ No newline at end of file diff --git a/computercraft/holoclock2.lua b/computercraft/holoclock2.lua new file mode 100644 index 0000000..87a586c --- /dev/null +++ b/computercraft/holoclock2.lua @@ -0,0 +1,177 @@ +peripheral.find("hologram", function(_, holo) holo.clear() end) +local hologram = peripheral.find("hologram") + +local date +local config = { + dateColor = 0xFFFFFF, + holoScale = 3 +} + +local symbols = { + ["0"] = { + { 0, 1, 1, 1, 0 }, + { 1, 0, 0, 0, 1 }, + { 1, 0, 0, 0, 1 }, + { 0, 0, 0, 0, 0 }, + { 1, 0, 0, 0, 1 }, + { 1, 0, 0, 0, 1 }, + { 0, 1, 1, 1, 0 }, + }, + ["1"] = { + { 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 1 }, + { 0, 0, 0, 0, 1 }, + { 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 1 }, + { 0, 0, 0, 0, 1 }, + { 0, 0, 0, 0, 0 }, + }, + ["2"] = { + { 0, 1, 1, 1, 0 }, + { 0, 0, 0, 0, 1 }, + { 0, 0, 0, 0, 1 }, + { 0, 1, 1, 1, 0 }, + { 1, 0, 0, 0, 0 }, + { 1, 0, 0, 0, 0 }, + { 0, 1, 1, 1, 0 }, + }, + ["3"] = { + { 0, 1, 1, 1, 0 }, + { 0, 0, 0, 0, 1 }, + { 0, 0, 0, 0, 1 }, + { 0, 1, 1, 1, 0 }, + { 0, 0, 0, 0, 1 }, + { 0, 0, 0, 0, 1 }, + { 0, 1, 1, 1, 0 }, + }, + ["4"] = { + { 0, 0, 0, 0, 0 }, + { 1, 0, 0, 0, 1 }, + { 1, 0, 0, 0, 1 }, + { 0, 1, 1, 1, 0 }, + { 0, 0, 0, 0, 1 }, + { 0, 0, 0, 0, 1 }, + { 0, 0, 0, 0, 0 }, + }, + ["5"] = { + { 0, 1, 1, 1, 0 }, + { 1, 0, 0, 0, 0 }, + { 1, 0, 0, 0, 0 }, + { 0, 1, 1, 1, 0 }, + { 0, 0, 0, 0, 1 }, + { 0, 0, 0, 0, 1 }, + { 0, 1, 1, 1, 0 }, + }, + ["6"] = { + { 0, 1, 1, 1, 0 }, + { 1, 0, 0, 0, 0 }, + { 1, 0, 0, 0, 0 }, + { 0, 1, 1, 1, 0 }, + { 1, 0, 0, 0, 1 }, + { 1, 0, 0, 0, 1 }, + { 0, 1, 1, 1, 0 }, + }, + ["7"] = { + { 0, 1, 1, 1, 0 }, + { 0, 0, 0, 0, 1 }, + { 0, 0, 0, 0, 1 }, + { 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 1 }, + { 0, 0, 0, 0, 1 }, + { 0, 0, 0, 0, 0 }, + }, + ["8"] = { + { 0, 1, 1, 1, 0 }, + { 1, 0, 0, 0, 1 }, + { 1, 0, 0, 0, 1 }, + { 0, 1, 1, 1, 0 }, + { 1, 0, 0, 0, 1 }, + { 1, 0, 0, 0, 1 }, + { 0, 1, 1, 1, 0 }, + }, + ["9"] = { + { 0, 1, 1, 1, 0 }, + { 1, 0, 0, 0, 1 }, + { 1, 0, 0, 0, 1 }, + { 0, 1, 1, 1, 0 }, + { 0, 0, 0, 0, 1 }, + { 0, 0, 0, 0, 1 }, + { 0, 1, 1, 1, 0 }, + }, + [":"] = { + { 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0 }, + { 0, 0, 1, 0, 0 }, + { 0, 0, 0, 0, 0 }, + { 0, 0, 1, 0, 0 }, + { 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0 }, + }, + ["."] = { + { 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0 }, + { 0, 0, 1, 0, 0 }, + } +} + +local function drawSymbolOnProjector(plane, x, y, symbol) + local xPos = x + for j = 1, #symbols[symbol] do + for i = 1, #symbols[symbol][j] do + if symbols[symbol][j][i] == 1 then + --hologram.set(xPos, y, z, 1) + plane[xPos * 32 + y] = 1 + else + --hologram.set(xPos, y, z, 0) + plane[xPos * 32 + y] = nil + end + xPos = xPos + 1 + end + xPos = x + y = y - 1 + end +end + +local function drawText(plane, x, y, text) + for i = 1, string.len(text) do + local symbol = string.sub(text, i, i) + drawSymbolOnProjector(plane, i * 6 + 4, 16, symbol) + end +end + +local function centerText(plane, text) + local textWidth = string.len(text) * 6 + local holoWidth = 48 + drawText(plane, math.floor(textWidth - (holoWidth / 2)), 1, text) +end + +hologram.clear() +hologram.setScale(3) +hologram.setTranslation(0, 0, 0) +hologram.setPaletteColor(1, config.dateColor) +hologram.setScale(config.holoScale) +hologram.setRotationSpeed(0, 0, 0, 0) +hologram.setRotation(90, 0, 1, 0) + +while true do + local time = os.time() + local hour = math.floor(time) + local min = math.floor((time - hour) * 60) + local plane = {} + centerText(plane, ("%02d:%02d"):format(hour, min)) + local data = {} + table.insert(data, ("\0"):rep(48 * 24 * 32)) + for z = 1, 48 do + for y = 1, 32 do + table.insert(data, plane[z * 32 + y] and "\1" or "\0") + --table.insert(data, "\1") + end + end + table.insert(data, ("\0"):rep(48 * 23 * 32)) + hologram.setRaw(table.concat(data)) + sleep(0.5) +end \ No newline at end of file diff --git a/computercraft/holoconstrained.lua b/computercraft/holoconstrained.lua new file mode 100644 index 0000000..f4fae25 --- /dev/null +++ b/computercraft/holoconstrained.lua @@ -0,0 +1,127 @@ +-- Perlin noise and more in pure Lua. +-- Found this on stackoverflow but can't find the URL anymore. +local noise = {} + +local p = {} +local permutation = {151,160,137,91,90,15, + 131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23, + 190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33, + 88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166, + 77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244, + 102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196, + 135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123, + 5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42, + 223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9, + 129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228, + 251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107, + 49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254, + 138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180 +} + +for i = 0, 255 do + p[i] = permutation[i + 1] + p[256 + i] = permutation[i + 1] +end + +local function fade(t) + return t * t * t * (t * (t * 6 - 15) + 10) +end + +local function lerp(t, a, b) + return a + t * (b - a) +end + +local function grad(hash, x, y, z) + local h, u, v = hash % 16 + if h < 8 then u = x else u = y end + if h < 4 then v = y elseif h == 12 or h == 14 then v = x else v = z end + local r + if h % 2 == 0 then r = u else r = -u end + if h % 4 == 0 then r = r + v else r = r - v end + return r +end + +function noise.perlin(x, y, z) + y = y or 0 + z = z or 0 + local X = math.floor(x % 255) + local Y = math.floor(y % 255) + local Z = math.floor(z % 255) + x = x - math.floor(x) + y = y - math.floor(y) + z = z - math.floor(z) + local u = fade(x) + local v = fade(y) + local w = fade(z) + + A = p[X ] + Y + AA = p[A ] + Z + AB = p[A + 1] + Z + B = p[X + 1] + Y + BA = p[B ] + Z + BB = p[B + 1] + Z + + return lerp(w, lerp(v, lerp(u, grad(p[AA ], x , y , z ), + grad(p[BA ], x - 1, y , z )), + lerp(u, grad(p[AB ], x , y - 1, z ), + grad(p[BB ], x - 1, y - 1, z ))), + lerp(v, lerp(u, grad(p[AA + 1], x , y , z - 1), + grad(p[BA + 1], x - 1, y , z - 1)), + lerp(u, grad(p[AB + 1], x , y - 1, z - 1), + grad(p[BB + 1], x - 1, y - 1, z - 1)))) +end + +function noise.fbm(x, y, z, octaves, lacunarity, gain) + octaves = octaves or 8 + lacunarity = lacunarity or 2 + gain = gain or 0.5 + local amplitude = 1.0 + local frequency = 1.0 + local sum = 0.0 + for i = 0, octaves do + sum = sum + amplitude * noise.perlin(x * frequency, y * frequency, z * frequency) + amplitude = amplitude * gain + frequency = frequency * lacunarity + end + return sum +end + +local holo = peripheral.find "hologram" + +local W = 48 +local H = 32 + +local perlin = noise.perlin + +holo.setPaletteColor(1, 0x0000FF) +holo.setPaletteColor(2, 0x00FFFF) +holo.setPaletteColor(3, 0x00FF00) + +holo.setScale(1/3) +while true do + local out = {} + for x = 1, W do + for z = 1, W do + local y = perlin(x / W * 4, z / W * 4, os.clock()) + y = math.floor((y + 1) * (H / 2)) + local apiaristics = perlin(x / W * 0.5, z / W * 0.5, os.clock() * 0.2 - 6) * 2.7 + apiaristics = math.min(1.9, math.max(-1.9, apiaristics)) + for Y = 1, y do + local color = 2 + if apiaristics < -0.2 then + if math.random() < (math.abs(apiaristics) - 0.2) then + color = 1 + end + elseif apiaristics > 0.2 then + if math.random() < (math.abs(apiaristics) - 0.2) then + color = 3 + end + end + table.insert(out, string.char(color)) + end --table.insert(out, (string.char(color)):rep(y) .. ("\x00"):rep(H - y)) + table.insert(out, ("\x00"):rep(H - y)) + end + end + holo.setRaw(table.concat(out)) + sleep(0.1) +end \ No newline at end of file diff --git a/computercraft/hologram.lua b/computercraft/hologram.lua new file mode 100644 index 0000000..a0d709d --- /dev/null +++ b/computercraft/hologram.lua @@ -0,0 +1,62 @@ +local holos = {peripheral.find "hologram"} +local translations = { + ["4bebae02"] = {0, 0, 0}, + ["721c3701"] = {0, 0, -1/3}, + ["30358553"] = {0, 0, 1/3} +} +local colors = { + 0xFF0000, + 0x00FF00, + 0x0000FF, + 0xFFFF00, + 0xFF00FF, + 0x00FFFF, + 0x000000, + 0xFFFFFF, + 0x888888 +} + +for i, holo in pairs(holos) do + holo.setTranslation(unpack(translations[peripheral.getName(holo):sub(1, 8)])) + for c = 1, 3 do + holo.setPaletteColor(c, colors[(i - 1) * 3 + c]) + end +end + +local W = 48 +local H = 32 + +local function generate_strings(fn) + local out = {} + for _ in pairs(holos) do + table.insert(out, {}) + end + for x = 1, W do + for z = 1, W do + for y = 1, H do + local color = fn(x, y, z) + local targ = math.ceil(color / 3) + for i, t in pairs(out) do + if i == targ then + table.insert(t, string.char((color - 1) % 3 + 1)) + else + table.insert(t, "\0") + end + end + end + end + end + return out +end + +local function random() + return math.random(0, 9) +end + +while true do + local s = generate_strings(random) + for i, holo in pairs(holos) do + holo.setRaw(table.concat(s[i])) + end + sleep(0.2) +end \ No newline at end of file diff --git a/computercraft/intruder-annoyer.lua b/computercraft/intruder-annoyer.lua new file mode 100644 index 0000000..c4c0cc2 --- /dev/null +++ b/computercraft/intruder-annoyer.lua @@ -0,0 +1,51 @@ +local authorized = settings.get "alarm.authorized" +local bounds = settings.get "alarm.bounds" +if type(bounds) == "string" then bounds = textutils.unserialise(bounds) end +local particle = peripheral.find "particle" +local sensor = peripheral.find "plethora:sensor" + +local function random_in_range(min, max) + local size = math.abs(min) + math.abs(max) + print(size, min, max) + return (math.random() * size) + min +end + +local function detect_intruders() + local es = sensor.sense() + local positions = {} + for _, e in pairs(es) do + if e.x >= bounds[1] and e.x <= bounds[2] and e.y >= bounds[3] and e.y <= bounds[4] and e.z >= bounds[5] and e.z <= bounds[6] then + table.insert(positions, { e.x, e.y, e.z }) + print(os.clock(), e.displayName) + if e.displayName == authorized then return false end + end + end + return positions +end + +while true do + local intruders = detect_intruders() + print(intruders) + if intruders and #intruders > 0 then + -- generally fill building + for _ = 1, 8 do + particle.spawn("barrier", + random_in_range(bounds[1], bounds[2]), + random_in_range(bounds[3], bounds[4]), + random_in_range(bounds[5], bounds[6])) + end + -- specifically target intruder + for _, position in pairs(intruders) do + local x, y, z = unpack(position) + for _ = 1, 16 do + particle.spawn("barrier", + random_in_range(x - 2, x + 2), + random_in_range(y - 2, y + 2), + random_in_range(z - 2, z + 2)) + end + end + sleep() + else + sleep(1) + end +end \ No newline at end of file diff --git a/computercraft/itemfilter.lua b/computercraft/itemfilter.lua new file mode 100644 index 0000000..6f74867 --- /dev/null +++ b/computercraft/itemfilter.lua @@ -0,0 +1,25 @@ +local enderchest = peripheral.find "minecraft:ender chest" +local targets = {} +for _, name in pairs(peripheral.call("right", "getNamesRemote")) do + if peripheral.getType(name) == "minecraft:ironchest_iron" then + table.insert(targets, name) + end +end +local discard = { + ["minecraft:cobblestone"] = true +} + +while true do + for slot, content in pairs(enderchest.list()) do + if discard[content.name] then + enderchest.drop(slot) + else + local remaining = content.count + for _, target in pairs(targets) do + remaining = remaining - enderchest.pushItems(target, slot) + if remaining == 0 then break end + end + end + end + sleep(1) +end \ No newline at end of file diff --git a/computercraft/keyctl.py b/computercraft/keyctl.py new file mode 100644 index 0000000..25a0d46 --- /dev/null +++ b/computercraft/keyctl.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +import requests +import argparse +import sys + +parser = argparse.ArgumentParser(description="Manage keys in SPUDNET") +parser.add_argument("--spudnet_url", "-s", required=True, help="URL of the SPUDNET instance to access") +parser.add_argument("--key", "-K", required=True) +subparsers = parser.add_subparsers(required=True) +subparsers.dest = "command" +subparsers.add_parser("info", help="Get information about this key.") +subparsers.add_parser("dependent_keys", help="List keys issued by this key.") +subparsers.add_parser("disable_key", help="Disable this key.") +issue_key = subparsers.add_parser("issue_key", help="Issue a new key.") +issue_key.add_argument("--channel", "-c", action="append", help="Allow new key access to this channel. Can be repeated. If none specified, defaults to issuing key's channels.") +issue_key.add_argument("--bearer", "-b", help="Specifies bearer of new key") +issue_key.add_argument("--use", "-u", help="Specifies intended use of new key") +issue_key.add_argument("--permission_level", "-p", help="Specifies permission level of new key. Not currently used. Must be <= issuing key's permission level", type=int) + +args = parser.parse_args() + +def query(subpath, data): + for key, value in list(data.items()): + if value == None: del data[key] + result = requests.post(args.spudnet_url + "/hki/" + subpath, json=data) + if not result.ok: + print(result.text) + sys.exit(1) + else: + return result.json() + +if args.command == "info": + print(query("key-info", { "key": args.key })) +elif args.command == "dependent_keys": + print(query("dependent-keys", { "key": args.key })) +elif args.command == "issue_key": + print(query("issue-key", { + "key": args.key, + "bearer": args.bearer, + "use": args.use, + "permission_level": args.permission_level, + "allowed_channels": args.channel + })) +elif args.command == "disable_key": + yn = input("Are you sure? All keys issued by this key will also be disabled. This action is irreversible. (y/n) ") + if yn == "y": + print(query("disable-key", { "key": args.key })) + else: + print("Action cancelled.") \ No newline at end of file diff --git a/computercraft/kristdump.py b/computercraft/kristdump.py new file mode 100644 index 0000000..5c6d44f --- /dev/null +++ b/computercraft/kristdump.py @@ -0,0 +1,41 @@ +import sqlite3 +import time +import requests +from datetime import datetime, timezone + +CHUNK = 1000 + +def do_query(offset): + return requests.get("https://krist.dev/transactions", params={"excludeMined": True, "limit": CHUNK, "offset": CHUNK * offset}).json() + +conn = sqlite3.connect("krist.sqlite3") +conn.row_factory = sqlite3.Row +conn.executescript("""CREATE TABLE IF NOT EXISTS tx ( + id INTEGER PRIMARY KEY, + fromaddr TEXT, + toaddr TEXT NOT NULL, + value INTEGER NOT NULL, + time INTEGER NOT NULL, + name TEXT, + meta TEXT, + sent_metaname TEXT, + sent_name TEXT +);""") + + +i = 0 +while True: + results = do_query(i) + print(list(results.keys())) + if results.get("count") == 0: + print("done") + break + elif results["ok"] == False and "rate limit" in results.get("error", ""): + print(results.get("error")) + time.sleep(90) + elif results["ok"] == False: + print(results.get("error")) + else: + conn.executemany("INSERT INTO tx VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", [ (x["id"], x["from"], x["to"], x["value"], int(datetime.strptime(x["time"], "%Y-%m-%dT%H:%M:%S.%f%z").astimezone(timezone.utc).timestamp() * 1000), x["name"], x["metadata"], x["sent_metaname"], x["sent_name"]) for x in results["transactions"] ]) + conn.commit() + i += 1 \ No newline at end of file diff --git a/computercraft/labelnet.lua b/computercraft/labelnet.lua new file mode 100644 index 0000000..3c1693b --- /dev/null +++ b/computercraft/labelnet.lua @@ -0,0 +1,117 @@ +-- https://github.com/SquidDev-CC/CC-Tweaked/blob/1c9110b9277bd7a2bf0b2ddd9d517656a72da906/src/main/java/dan200/computercraft/shared/util/StringUtil.java#L11-L31 is the code restricting the contents of labels. +-- Basically, they can contain chars (I think in Extended ASCII for some reason, and inclusive) 32 to 126, 161 to 172, and 174 to 255. This gives us 187 (EDIT: 189) options to work with, thus base 187 (189). + +-- BaseN encoding/decoding blob +-- https://github.com/oploadk/base2base for computercraft + +local a=string.format;local function b(c,d,e)local f,g,h={}for i=1,#c do g,h=i,c[i]*d;while true do h=(f[g]or 0)+h;f[g]=h%e;h=math.floor(h/e)if h==0 then break end;g=g+1 end end;return f end;local function j(k,l,m,n)local g,h;for i=1,#m do g,h=i,l*(m[i]or 0)while true do h=(k[g]or 0)+h;k[g]=h%n;h=math.floor(h/n)if h==0 then break end;g=g+1 end end end;local function o(self,p)local f,q={},#p;for i=1,q do f[i]=self.r_alpha_from[p:byte(q-i+1)]end;return f end;local function r(self,h)local f,q={},#h;for i=q,1,-1 do f[q-i+1]=self.alpha_to:byte(h[i]+1)end;return string.char(table.unpack(f))end;local function s(self,l)return self.power[l]or b(s(self,l-1),self.base_from,self.base_to)end;local function t(self,h)local f={}for i=1,#h do j(f,h[i],s(self,i-1),self.base_to)end;return f end;local function u(self,p)return r(self,t(self,o(self,p)))end;local function v(self,p)for i=1,#p do if not self.r_alpha_from[p:byte(i)]then return false end end;return true end;local w={__index={convert=u,validate=v},__call=function(self,p)return self:convert(p)end}function new_converter(x,y)local self={alpha_to=y,base_from=#x,base_to=#y}local z={}for i=1,#x do z[x:byte(i)]=i-1 end;self.r_alpha_from=z;self.power={[0]={1}}return setmetatable(self,w)end + +local function byte_table_to_string(bytes) + local str = "" + for _, x in ipairs(bytes) do + str = str .. string.char(x) + end + return str +end + +local function get_byte(num, byte) + return bit.band(bit.brshift(num, byte * 8), 0xFF) +end + +local function from_bytes(b) + local n = 0 + for ix, byte in pairs(b) do + n = bit.bor(n, bit.blshift(byte, (ix - 1) * 8)) + end + return n +end + +local ascii = {} +for i = 0, 255 do table.insert(ascii, i) end +local label_charset = {} +for i = 32, 126 do table.insert(label_charset, i) end +for i = 161, 172 do table.insert(label_charset, i) end +for i = 174, 255 do table.insert(label_charset, i) end +label_charset = byte_table_to_string(label_charset) +ascii = byte_table_to_string(ascii) + +local from_label = new_converter(label_charset, ascii) +local to_label = new_converter(ascii, label_charset) + +local states = { + IDLE = 0, + TRANSMITTING = 1, + RECEIVING = 2, + RECEIVE_ERROR = 3 +} + +local function receive_data(side) + local out = {} + repeat sleep(0.05) until rs.getBundledInput(side) == states.TRANSMITTING + rs.setBundledOutput(side, states.RECEIVING) + local last + local xseq = 1 + repeat + local label = peripheral.call(side, "getLabel") + if label then + local received = from_label(label) + if received ~= last then + local seq, rest = received:byte(1), received:sub(2) + if seq ~= xseq then + print("expected", xseq, "got", seq) + end + last = received + xseq = xseq + 1 + table.insert(out, rest) + end + end + sleep(0.05) + until rs.getBundledInput(side) ~= states.TRANSMITTING + rs.setBundledOutput(side, states.IDLE) + return table.concat(out) +end + +local function send_data(side, data) + local packets = {} + local packet_index = 1 + local remaining, chunk = data + while true do + chunk, remaining = remaining:sub(1, 29), remaining:sub(30) + local header = string.char(get_byte(packet_index, 0)) + table.insert(packets, header .. chunk) + packet_index = packet_index + 1 + if #remaining == 0 then break end + end + local label = os.getComputerLabel() + rs.setBundledOutput(side, states.TRANSMITTING) + repeat sleep(0.05) until rs.getBundledInput(side) == states.RECEIVING + for _, packet in ipairs(packets) do + os.setComputerLabel(to_label(packet)) + sleep(0.05) + end + rs.setBundledOutput(side, states.IDLE) + sleep(0.1) + os.setComputerLabel(label) +end + +local other +for _, name in pairs(peripheral.getNames()) do + for _, method in pairs(peripheral.getMethods(name)) do + if method == "getLabel" then + other = name + break + end + end +end + +local option = ... + +if option == "send" then + write "Send: " + local text = read() + send_data(other, text) +elseif option == "receive" then + print(receive_data(other)) +end + +return { to_label = to_label, from_label = from_label, send_data = send_data, receive_data = receive_data } diff --git a/computercraft/laser_tbm.lua b/computercraft/laser_tbm.lua new file mode 100644 index 0000000..813e292 --- /dev/null +++ b/computercraft/laser_tbm.lua @@ -0,0 +1,90 @@ +local laser = peripheral.find "plethora:laser" +local modem = peripheral.find "modem" +local channel = 26535 +local count = 8 +local go = false +modem.open(channel) + +--[[ +local movement_notifications = {} +local function moved_count() + local c = 0 + for k in pairs(movement_notifications) do + c = c + 1 + end + return c +end +]] + +local function main() + while true do + --print("reset movement notifications") + --movement_notifications = {} + --while true do + --[[if turtle.detect() then + laser.fire(-180, 0, 5) + --laser.fire(0, 0, 5) + end + local ok, reason = turtle.forward() + if ok then + print("transmit movement notification") + modem.transmit(channel, channel, { "moved", os.getComputerID() }) + break + elseif reason == "Out of fuel" then + print("Refuel") + turtle.refuel() + sleep(1) + end]] + if go then laser.fire(270, 0, 5) else sleep(1) end + --end + --[[ + local calls = {} + for i = 1, 16 do + table.insert(calls, function() laser.fire(0, 90, 5) end) + end + parallel.waitForAll(unpack(calls)) + --laser.fire(0, -270, 5) + while (not go or moved_count() ~= (count - 1)) do + print("count is", moved_count(), moved_count() == count - 1, "go", go) + os.pullEvent() + end + ]] + end +end + +local function communications() + while true do + local _, _, c, rc, msg, distance = os.pullEvent "modem_message" + if c == channel and type(msg) == "table" then + if msg[1] == "ping" then + modem.transmit(channel, channel, { "pong", gps.locate() }) + elseif distance and msg[1] == "stop" and distance < 32 then + print("stop command") + go = false + elseif distance and msg[1] == "start" and distance < 32 then + print("start command") + go = true + elseif distance and msg[1] == "moved" and distance < 32 then + print("got movement notification") + movement_notifications[msg[2]] = true + elseif distance and msg[1] == "update" and distance < 32 then + local h = http.get "https://osmarks.net/stuff/laser_tbm.lua" + local t = h.readAll() + h.close() + local f, e = load(t) + if not f then printError(e) + else + local f = fs.open("startup", "w") + f.write(t) + f.close() + print "updated" + os.reboot() + end + elseif distance and msg[1] == "forward" and distance < 32 then + turtle.forward() + end + end + end +end + +parallel.waitForAll(communications, main) \ No newline at end of file diff --git a/computercraft/lms.lua b/computercraft/lms.lua new file mode 100644 index 0000000..e3a96f7 --- /dev/null +++ b/computercraft/lms.lua @@ -0,0 +1,130 @@ +local function update() + local h = http.get "https://pastebin.com/raw/L0ZKLBRG" + local f = fs.open(shell.getRunningProgram(), "w") + f.write(h.readAll()) + f.close() + h.close() +end + +if ... == "update" then update() end + +local m = peripheral.find "modem" +local s = peripheral.find "speaker" + +local chan = 3636 +print "Welcome to the Lightweight Messaging System (developed by GTech Potatronics)" + +m.open(chan) + +local username = settings.get "lms.username" +if username == nil then + write "Username: " + username = read() +end + +local w, h = term.getSize() +local send_window = window.create(term.current(), 1, h, w, 1) +local message_window = window.create(term.current(), 1, 1, w, h - 1) + +local function notification_sound() + if s then + for i = 4, 12, 4 do + s.playNote("flute", 3, i) + sleep(0.2) + end + end +end + +local function exec_in_window(w, f) + local x, y = term.getCursorPos() + local last = term.redirect(w) + f() + term.redirect(last) + w.redraw() + term.setCursorPos(x, y) +end + +local function print_message(txt) + exec_in_window(message_window, function() + print(txt) + end) +end + +local function trim(s) + return s:match( "^%s*(.-)%s*$" ) +end + +local banned_text = { + "yeet", + "ecs dee", + "dab", +} + +if debug and debug.getmetatable then + _G.getmetatable = debug.getmetatable +end + +local function to_case_insensitive(text) + return text:gsub("[a-zA-Z]", function(char) return ("[%s%s]"):format(char:lower(), char:upper()) end) +end + +local function filter(text) + local out = text + for _, b in pairs(banned_text) do + out = out:gsub(to_case_insensitive(b), "") + end + return out +end + +local function strip_extraneous_spacing(text) + return text:gsub("%s+", " ") +end + +local function collapse_e_sequences(text) + return text:gsub("ee+", "ee") +end + +local function preproc(text) + return trim(filter(strip_extraneous_spacing(collapse_e_sequences(text:sub(1, 128))))) +end + +local function add_message(msg, usr) + local msg, usr = preproc(msg), preproc(usr) + if msg == "" or usr == "" then return end + print_message(usr .. ": " .. msg) +end + +local function send() + term.redirect(send_window) + term.setBackgroundColor(colors.white) + term.setTextColor(colors.black) + term.clear() + local hist = {} + while true do + local msg = read(nil, hist) + if msg == "!!exit" then return + elseif msg == "!!update" then update() print_message "Updated. Please restart the program." + else + table.insert(hist, msg) + if preproc(msg) == "" then + print_message "Your message is considered spam." + else + add_message(msg, username) + m.transmit(chan, chan, { message = msg, username = username }) + end + end + end +end + +local function recv() + while true do + local _, _, channel, _, message = os.pullEvent "modem_message" + if channel == chan and type(message) == "table" and message.message and message.username then + notification_sound() + add_message(message.message, message.username) + end + end +end + +m.transmit(chan, chan, { username = username, message = "Connected." }) +parallel.waitForAny(send, recv) \ No newline at end of file diff --git a/computercraft/lua-adt.lua b/computercraft/lua-adt.lua new file mode 100644 index 0000000..5e73632 --- /dev/null +++ b/computercraft/lua-adt.lua @@ -0,0 +1,228 @@ +local function trim(str, chars) + if chars == nil then + chars = "%s*" + end + return string.match(str, "^"..chars.."(.-)"..chars.."$") +end + +local function split(str, delim) + if delim == nil then + delim = "\n" + end + local t = {} + for s in string.gmatch(str, "([^"..delim.."]+)") do + table.insert(t, trim(s)) + end + return t +end + +local function find_multiple(str, patterns, offset) + local ws = string.len(str) + local we = we + local wpattern = nil + for i, pattern in pairs(patterns) do + s, e = string.find(str, pattern, offset) + if s ~= nil then + if s < ws then + ws = s + we = e + wpattern = pattern + end + end + end + return ws, we, wpattern +end + +local function balanced_end(str, word, offset) + if offset == nil then + offset = 1 + end + local i = offset + while true do + local s, e, p + if word == "then" then + s, e, p = find_multiple(str, {"%smatch%s", "%sfunction%s", "%sthen%s", "%sdo%s", "%send%s", "%selseif%s"}, i) + else + s, e, p = find_multiple(str, {"%smatch%s", "%sfunction%s", "%sthen%s", "%sdo%s", "%send%s"}, i) + end + if p == "%send%s" then + return e + elseif p == "%selseif%s" then + return e + elseif p == nil then + return "UNBAL" + end + i = balanced_end(str, string.sub(p, 3, -3), e) + if i == "UNBAL" then + return i + end + end +end + +local function get_decls(str) + -- gather data declarations from source & remove + local datas = {} + local i = 0 + local strout = "" + while true do + local n, a = string.find(str, "%sdata [%w]+", i+1) + if n == nil then + strout = strout .. string.sub(str, i+1) + break + end + strout = strout .. string.sub(str, i+1, n) + local e, d = string.find(str, "end", i+1) + local cont = string.sub(str, a+1, e-1) + local data = {} + for i, case in ipairs(split(cont)) do + local c = {} + local b, p = string.find(case, "%(") + if b == nil then + c.name = case + c.args = 0 + else + c.name = string.sub(case, 1, p-1) + c.args = #split(case, ",") + end + table.insert(data, c) + end + i = d + table.insert(datas, data) + end + return datas, strout +end + +local parseexprs, replace_case, replace_match +local function parseexpr(str) + local n, o, name, body = string.find(str, "(%w+)(%b())") + if n == nil then + local b = string.find(str, ",") + if b == nil then + return {type="var",name=str}, "" + else + return {type="var", name=string.sub(str, 0, b-1)}, string.sub(str,b+1) + end + end + body = string.sub(body, 2, -2) + local obj = {type="data", name=name, body=parseexprs(body)} + local rem = string.sub(str, o+1) + local b = string.find(rem, ",") + if b == nil then + return obj, "" + else + return obj, string.sub(rem,b+1) + end +end + +parseexprs = function(str) + local t = {} + while str ~= "" do + local obj + obj, str = parseexpr(str) + table.insert(t, obj) + end + return t +end + +local function getCase(datas, data) + for i, x in ipairs(datas) do + for i, case in ipairs(x) do + if case.name == data then + return i + end + end + end +end + +local function comparison(datas, var, pattern) + if pattern.type == "data" then + local out = var .. ".case == " .. getCase(datas, pattern.name) + for i, x in ipairs(pattern.body) do + if x.type == "data" then + out = out .. " and " .. comparison(datas, var .. "[" .. i .. "]", x) + end + end + return out + else + return "true" + end +end + +local function destructure(datas, var, pattern) + if pattern.type == "var" then + return "local " .. pattern.name .. " = " .. var + else + local out = "" + for i, x in ipairs(pattern.body) do + out = out .. "\n" .. destructure(datas, var .. "[" .. i .. "]", x) + end + return out + end +end + +replace_match = function(datas, str) + while true do + local m, b, var = string.find(str, "%smatch (%w+)%s") + if m == nil then + return str + end + local e = balanced_end(str, "match", b) + local cont = string.sub(str, b, e-4) + str = string.sub(str, 1, m) .. replace_case(datas, cont, var) .. string.sub(str, e, -1) + end +end + +replace_case = function(datas, out, var) + local str = out + local count = 0 + while true do + local m + local b + local p + m, b, p = string.find(str, "%scase ([^%s]+) do%s") + if m == nil then + for i=1,count do + str = str .. "\nend" + end + return str + end + count = count + 1 + local e = balanced_end(str, "do", b) + local cont = string.sub(str, b, e-5) + local pattern = parseexpr(p) + local bool = comparison(datas, var, pattern) + local body = destructure(datas, var, pattern) + str = string.sub(str, 1, m) .. "\nif " .. bool .. " then\n" .. body .. cont .. "\nelse\n" .. string.sub(str, e, -1) + end +end + +local function printpattern(pattern) + if pattern.type == "data" then + io.write(pattern.name) + io.write("(") + for i, v in ipairs(pattern.body) do + printpattern(v) + io.write(",") + end + io.write(")") + else + io.write(pattern.name) + end +end + +local function writeheaders(datas) + local out = "" + for i, x in ipairs(datas) do + for i, case in ipairs(x) do + out = out .. "\nlocal function " .. case.name .. "(...) return {case=" .. i .. ",...} end" + end + end + return out +end + +function preprocess(str) + local decls, str0 = get_decls(str) + local b = replace_match(decls, str0) + local h = writeheaders(decls) + return h .. b +end \ No newline at end of file diff --git a/computercraft/matrixrain.lua b/computercraft/matrixrain.lua new file mode 100644 index 0000000..a5e07d4 --- /dev/null +++ b/computercraft/matrixrain.lua @@ -0,0 +1,41 @@ +local mat = peripheral.wrap "right" +mat.setPaletteColor(colors.black, 0) +mat.setPaletteColor(colors.green, 0x15b01a) +mat.setPaletteColor(colors.lime, 0x01ff07) +mat.setTextScale(0.5) +local w, h = mat.getSize() + +local function rchar() + return string.char(math.random(0, 255)) +end + +local function wrap(x) + return (x - 1) % h + 1 +end + +local cols = {} +for i = 1, w do + local base = math.random(1, h) + table.insert(cols, { base, base + math.random(1, h - 5) }) +end + +while true do + for x, col in pairs(cols) do + local start = col[1] + local endp = col[2] + mat.setCursorPos(x, start) + mat.write " " + mat.setCursorPos(x, wrap(endp - 1)) + mat.setTextColor(colors.green) + mat.write(rchar()) + mat.setTextColor(colors.lime) + mat.setCursorPos(x, endp) + mat.write(rchar()) + col[1] = col[1] + 1 + col[2] = col[2] + 1 + + col[1] = wrap(col[1]) + col[2] = wrap(col[2]) + end + sleep(0.1) +end \ No newline at end of file diff --git a/computercraft/mekfr.lua b/computercraft/mekfr.lua new file mode 100644 index 0000000..483719d --- /dev/null +++ b/computercraft/mekfr.lua @@ -0,0 +1,42 @@ +local modem = {peripheral.find("modem", function(_, o) return o.isWireless() end)} +local gas_tanks = {peripheral.find "Ultimate Gas Tank"} +local name = os.getComputerLabel() +local function send(...) + for _, modem in pairs(modem) do + modem.transmit(48869, 48869, {...}) + end +end + +local fuel_depletion_latch = false +local reactor = peripheral.find "Reactor Logic Adapter" + +while true do + if type(reactor.getEnergy()) == "string" then + print("anomalous stringment", reactor.getEnergy()) + else + local energy = reactor.getEnergy() / reactor.getMaxEnergy() + send("mek_reactor_energy/" .. name, "fraction of fusion reactor's energy buffer which is full", energy) + send("mek_reactor_plastemp/" .. name, "reported plasma temperature", reactor.getPlasmaHeat()) + send("mek_reactor_casetemp/" .. name, "reported case temperature", reactor.getCaseHeat()) + local total_stored, total_max = 0, 0 + for _, tank in pairs(gas_tanks) do + total_max = total_max + tank.getMaxGas() + total_stored = total_stored + tank.getStoredGas() + end + local tritium = total_stored / total_max + send("mek_reactor_tritium/" .. name, "fraction of tritium buffer filled", tritium) + if not fuel_depletion_latch and tritium < 0.5 then + print "WARNING: Contingency Beta-4 initiated." + fuel_depletion_latch = true + end + local injection_rate = math.floor(3 + 17 * (math.min(1, 1.1 - energy) / 0.9)) * 2 + print(injection_rate) + if fuel_depletion_latch then + injection_rate = 6 + end + reactor.setInjectionRate(fuel_depletion_latch and 6 or injection_rate) + send("mek_reactor_injectionrate/" .. name, "fuel injection rate (set by controller)", injection_rate) + send("mek_reactor_powerout_rft/" .. name, "power output (RF/t)", reactor.getProducing() * 0.4) + end + sleep(1) +end \ No newline at end of file diff --git a/computercraft/modem-logger.lua b/computercraft/modem-logger.lua new file mode 100644 index 0000000..02a826e --- /dev/null +++ b/computercraft/modem-logger.lua @@ -0,0 +1,98 @@ +local m = peripheral.find "modem" +local o = peripheral.find "monitor" + +o.setTextScale(0.5) + +local w, h = o.getSize() +local command_window = window.create(o, 1, h, w, 1) +local outputs_window = window.create(o, 1, 1, w, h - 1) + +local function exec_in_window(w, f) + local x, y = o.getCursorPos() + local last = term.redirect(w) + f() + term.redirect(last) + w.redraw() + o.setCursorPos(x, y) +end + +local function print_output(txt, color) + exec_in_window(outputs_window, function() + term.setTextColor(color or colors.white) + print(txt) + end) +end + +local function splitspace(str) + local tokens = {} + for token in string.gmatch(str, "[^%s]+") do + table.insert(tokens, token) + end + return tokens +end + +local function controls() + term.redirect(command_window) + term.setBackgroundColor(colors.white) + term.setTextColor(colors.black) + term.clear() + local hist = {} + while true do + write "> " + local msg = read(nil, hist) + table.insert(hist, msg) + local tokens = splitspace(msg) + local command = table.remove(tokens, 1) + if command == "open" then + local chan = tonumber(tokens[1]) + m.open(chan) + print_output(("Opened %d"):format(chan), colors.gray) + elseif command == "close" then + local chan = tonumber(tokens[1]) + m.close(chan) + print_output(("Closed %d"):format(chan), colors.gray) + else + print_output("Command invalid", colors.gray) + end + end +end + +local function compact_serialize(x) + local t = type(x) + if t == "number" then + return tostring(x) + elseif t == "string" then + return textutils.serialise(x) + elseif t == "table" then + local out = "{ " + for k, v in pairs(x) do + out = out .. string.format("[%s]=%s, ", compact_serialize(k), compact_serialize(v)) + end + return out .. "}" + elseif t == "boolean" then + return tostring(x) + else + error("Unsupported type " .. t) + end +end + +local function safe_serialize(m) + local ok, res = pcall(compact_serialize, m) + if ok then return res + else return ("[UNSERIALIZABLE %s: %s]"):format(tostring(m), res) end +end + +local function tostring_with_default(x) + if not x then return "[UNKNOWN]" + else return tostring(x) end +end + +local function receive() + while true do + local _, _, channel, reply_channel, message, distance = os.pullEvent "modem_message" + print_output(("%d \16 %d | %s"):format(channel, reply_channel, tostring_with_default(distance))) + print_output(safe_serialize(message), colors.lightGray) + end +end + +parallel.waitForAny(controls, receive) \ No newline at end of file diff --git a/computercraft/modem2metrics.lua b/computercraft/modem2metrics.lua new file mode 100644 index 0000000..0a45c88 --- /dev/null +++ b/computercraft/modem2metrics.lua @@ -0,0 +1,19 @@ +local send_metric = require "metrics_interface" +peripheral.find("modem", function(_, o) o.open(48869) end) + +while true do + local _, _, c, rc, m = os.pullEvent "modem_message" + if type(m) == "table" then + print(unpack(m)) + send_metric(unpack(m)) + end +end + +--[[ +local modem = {peripheral.find("modem", function(_, o) return o.isWireless() end)} +local function send(...) + for _, modem in pairs(modem) do + modem.transmit(48869, 48869, {...}) + end +end +]] \ No newline at end of file diff --git a/computercraft/motion_integrator_test.lua b/computercraft/motion_integrator_test.lua new file mode 100644 index 0000000..cb3d680 --- /dev/null +++ b/computercraft/motion_integrator_test.lua @@ -0,0 +1,28 @@ +local ni = peripheral.wrap "back" +package.path = "/?;/?.lua;" .. package.path +local gps_patch = require "gps_patch" + +local estimated_position = vector.new(gps_patch.locate()) +local function integrate_motion() + local lt = os.clock() + while true do + local meta = ni.getMetaOwner() + local v = vector.new(meta.deltaPosX, meta.deltaPosY, meta.deltaPosZ) + --if math.floor(os.clock()) == os.clock() then print("vel", v) end + local time = os.clock() + local dt = time - lt + estimated_position = estimated_position + v + --estimated_position = channelwise(round_to_frac, estimated_position, meta.withinBlock) + lt = time + end +end + +local function compare_against_gps() + while true do + local pos = vector.new(gps_patch.locate()) + print("delta", pos - estimated_position) + sleep(1) + end +end + +parallel.waitForAll(integrate_motion, compare_against_gps) \ No newline at end of file diff --git a/computercraft/ncfr.lua b/computercraft/ncfr.lua new file mode 100644 index 0000000..dc913c0 --- /dev/null +++ b/computercraft/ncfr.lua @@ -0,0 +1,21 @@ +local s = "back" +local fr = peripheral.find "nc_fusion_reactor" +local name = ("%s_%s_%d"):format(fr.getFirstFusionFuel(), fr.getSecondFusionFuel(), os.getComputerID()) +local m = peripheral.find("modem", function(_, o) return o.isWireless() end) + +local function send_metric(...) + m.transmit(3054, 3054, {...}) +end + +local NC_HEAT_CONSTANT = 1218.76 + +while true do + local l = fr.getEnergyStored() / fr.getMaxEnergyStored() + local target_temp = fr.getFusionComboHeatVariable() * NC_HEAT_CONSTANT * 1000 + local temp = fr.getTemperature() + send_metric("reactor_energy/" .. name, "energy stored", "set", l) + send_metric("fusion_efficiency/" .. name, "efficiency of fusion reactor 0 to 100", "set", fr.getEfficiency()) + send_metric("fusion_temp/" .. name, "temperature of fusion reactor, relative to optimum", "set", temp / target_temp) + print(temp / target_temp, l) + sleep(1) +end \ No newline at end of file diff --git a/computercraft/ncfroc.lua b/computercraft/ncfroc.lua new file mode 100644 index 0000000..f678cd6 --- /dev/null +++ b/computercraft/ncfroc.lua @@ -0,0 +1,45 @@ +local component, computer = component, computer +if require then component = require "component" computer = require "computer" end +local wlan = component.proxy(component.list "modem"()) +local computer_peripheral = component.proxy(component.list "computer"()) +local reactor = component.proxy(component.list "nc_fusion_reactor"()) +local gpu = component.proxy(component.list "gpu"()) +local screen = component.list "screen"() +gpu.bind(screen) +wlan.setWakeMessage("poweron", true) + +local function display(txt) + local w, h = gpu.getResolution() + gpu.set(1, 1, txt .. (" "):rep(w - #txt)) +end + +local function sleep(timeout) + local deadline = computer.uptime() + (timeout or 0) + repeat + computer.pullSignal(deadline - computer.uptime()) + until computer.uptime() >= deadline +end + +computer_peripheral.beep(400) +display "Initialized" + +local NC_HEAT_CONSTANT = 1218.76 + +local last = nil + +while true do + local target_temp = reactor.getFusionComboHeatVariable() * NC_HEAT_CONSTANT * 1000 + local temp = reactor.getTemperature() + display(("%f %f"):format(temp / target_temp, reactor.getEfficiency())) + local too_high = temp > target_temp + if too_high then + if too_high ~= last then computer_peripheral.beep(800) end + reactor.deactivate() + else + if too_high ~= last then computer_peripheral.beep(500) end + reactor.activate() + end + last = too_high + wlan.broadcast(1111, "poweron") + sleep(0.5) +end \ No newline at end of file diff --git a/computercraft/ni-ctl.lua b/computercraft/ni-ctl.lua new file mode 100644 index 0000000..2147cbf --- /dev/null +++ b/computercraft/ni-ctl.lua @@ -0,0 +1,674 @@ +local username = settings.get "username" or "gollark" +local ni = peripheral.wrap "back" +local speaker = peripheral.find "speaker" +local modem = peripheral.find "modem" +local offload_laser = settings.get "offload_laser" +local w, h = ni.canvas().getSize() +if _G.thing_group then + pcall(_G.thing_group.remove) +end +if _G.canvas3d_group then + pcall(_G.canvas3d_group.clear) + pcall(_G.canvas3d_group.remove) +end +local group +local group_3d +local function initialize_group_thing() + if group then pcall(group.remove) end + if group_3d then pcall(group_3d.remove) end + group = ni.canvas().addGroup({ w - 70, 10 }) + ni.canvas3d().clear() + group_3d = ni.canvas3d().create() + _G.thing_group = group + _G.canvas3d_group = group_3d +end +initialize_group_thing() + +local targets = {} + +local use_spudnet = offload_laser + +local spudnet_send, spudnet_background +if use_spudnet then + print "SPUDNET interface loading." + spudnet_send, spudnet_background = require "ni-ctl_spudnet_interface"() +end + +local function offload_protocol(...) + spudnet_send { "exec", {...} } +end + +local function is_target(name) + for target, type in pairs(targets) do + if name:lower():match(target) then return type end + end +end + +local function vector_sqlength(self) + return self.x * self.x + self.y * self.y + self.z * self.z +end + +local function project(line_start, line_dir, point) + local t = (point - line_start):dot(line_dir) / vector_sqlength(line_dir) + return line_start + line_dir * t, t +end + +local function calc_yaw_pitch(v) + local x, y, z = v.x, v.y, v.z + local pitch = -math.atan2(y, math.sqrt(x * x + z * z)) + local yaw = math.atan2(-x, z) + return math.deg(yaw), math.deg(pitch) +end + +local settings_cfg = { + brake = { type = "bool", default = true, shortcode = "b" }, + counter = { type = "bool", default = false, shortcode = "c" }, -- counterattack + highlight = { type = "bool", default = false, shortcode = "h" }, + dodge = { type = "bool", default = true, shortcode = "d" }, + power = { type = "number", default = 5, max = 5, min = 0.5 }, -- laser power + flight = { type = "string", default = "std", shortcode = "f", alias = { fly = true } }, + drill = { type = "bool", default = false, shortcode = "D", persist = false }, + show_acceleration = { type = "bool", default = false }, + pitch_controls = { type = "bool", default = false }, + ext_highlight = { type = "bool", default = false } +} +local SAVEFILE = "ni-ctl-settings" +if fs.exists(SAVEFILE) then + local f = fs.open(SAVEFILE, "r") + for key, value in pairs(textutils.unserialise(f.readAll())) do + settings_cfg[key].value = value + end + f.close() +end +local gsettings = {} +setmetatable(gsettings, { + __index = function(_, key) + local cfg = settings_cfg[key] + if cfg.value == nil then return cfg.default else return cfg.value end + end, + __newindex = function(_, key, value) + print("set", key, "to", value) + settings_cfg[key].value = value + if settings_cfg[key].persist ~= false then + local kv = {} + for key, cfg in pairs(settings_cfg) do + kv[key] = cfg.value + end + local f = fs.open(SAVEFILE, "w") + f.write(textutils.serialise(kv)) + f.close() + end + os.queueEvent "settings_change" + end +}) + +local work_queue = {} + +local addressed_lasers = {} +local function bool_to_yn(b) + if b == true then return "y" + elseif b == false then return "n" + else return "?" end +end + +local status_lines = {} +local notices = {} +local function push_notice(t) + table.insert(notices, { t, os.epoch "utc" }) +end + +local function lase(entity) + local target_location = entity.s + for i = 1, 5 do + target_location = entity.s + entity.v * (target_location:length() / 1.5) + end + local y, p = calc_yaw_pitch(target_location) + if offload_laser then offload_protocol("fire", y, p, gsettings.power) else ni.fire(y, p, gsettings.power) end +end + +local user_meta +local fast_mode_reqs = {} + +local colortheme = { + status = 0xFFFFFFFF, + notice = 0xFF8800FF, + follow = 0xFF00FFFF, + watch = 0xFFFF00FF, + laser = 0xFF0000FF, + entity = 0x00FFFFFF, + select = 0x00FF00FF +} + +local function schedule(fn, time, uniquename) + if uniquename then + work_queue[uniquename] = { os.clock() + time, fn } + else + table.insert(work_queue, { os.clock() + time, fn }) + end +end + +local function direction_vector(yaw, pitch) + return vector.new( + -math.sin(math.rad(yaw)) * math.cos(math.rad(pitch)), + -math.sin(math.rad(pitch)), + math.cos(math.rad(yaw)) * math.cos(math.rad(pitch)) + ) +end + +--[[ +local function ni.launch(yaw, pitch, power) + ni.ni.launch(yaw, pitch, power) + if user_meta then + local impulse = vector.new( + -math.sin(math.rad(yaw)) * math.cos(math.rad(pitch)), + math.cos(math.rad(yaw)) * math.cos(math.rad(pitch)), + -math.sin(math.rad(pitch)) + ) + if user_meta.isElytraFlying then + impulse = 0.4 * impulse + end + user_meta.velocity = user_meta.velocity + (impulse * power) + end +end]] + +local gravity_motion_offset = 0.07840001 + +--[[ +local inav_position = nil +local inav_delta = nil +local scaler = 20 + +local function navigation() + while true do + local real_pos = vector.new(gps.locate()) + if inav_position then + print(inav_position - real_pos) + local real_delta = (inav_position - real_pos):length() + local delta_size = inav_delta:length() + print("calculated delta was", delta_size / real_delta, "of real") + end + inav_position = real_pos + inav_delta = vector.new(0, 0, 0) + sleep(3) + end +end +]] + +local hud_entities = {} +local function render_hud() + local flags = {} + for key, cfg in pairs(settings_cfg) do + if cfg.shortcode and cfg.type == "bool" then + if gsettings[key] then + table.insert(flags, cfg.shortcode) + end + end + end + table.sort(flags) + status_lines.flags = "Flags: " .. table.concat(flags) + + local i = 0 + local ok, err = pcall(group.clear) + if not ok then + initialize_group_thing() + end + local time = os.epoch "utc" + for _, text in pairs(status_lines) do + group.addText({ 0, i * 7 }, text, colortheme.status, 0.6) + i = i + 1 + end + for ix, info in pairs(notices) do + if time >= (info[2] + 2000) then notices[ix] = nil end + group.addText({ 0, i * 7 }, info[1], colortheme.notice, 0.6) + i = i + 1 + end + for thing, count in pairs(hud_entities) do + local text = thing + if count ~= 1 then text = text .. " " .. count end + group.addText({ 0, i * 7 }, text, colortheme[is_target(thing) or "entity"], 0.6) + i = i + 1 + end +end + +local last_velocity +local last_time +local integrated_position = vector.new(0, 0, 0) + +local function update_motion_vars(new_meta) + local time = os.clock() + if user_meta then + -- walking hack + if not (user_meta.isFlying or user_meta.isElytraFlying) then + if user_meta.isInWater then new_meta.motionY = new_meta.motionY + 0.02 + else new_meta.motionY = new_meta.motionY + gravity_motion_offset end + end + user_meta.velocity = vector.new(new_meta.motionX, new_meta.motionY, new_meta.motionZ) + user_meta.real_velocity = vector.new(new_meta.deltaPosX, new_meta.deltaPosY, new_meta.deltaPosZ) + integrated_position = integrated_position + user_meta.real_velocity + user_meta.motionX = new_meta.motionX + user_meta.motionY = new_meta.motionY + user_meta.motionZ = new_meta.motionZ + user_meta.pitch = new_meta.pitch + user_meta.yaw = new_meta.yaw + if last_time and last_velocity then + local timestep = time - last_time + user_meta.acceleration = (user_meta.velocity - last_velocity) / timestep + end + last_velocity = user_meta.velocity + end + last_time = time +end + +local function scan_entities() + while true do + fast_mode_reqs.laser = false + fast_mode_reqs.acting = false + local entities = ni.sense() + local maybe_players = {} + hud_entities = {} + local lasers = {} + + for _, entity in pairs(entities) do + entity.s = vector.new(entity.x, entity.y, entity.z) + entity.v = vector.new(entity.motionX, entity.motionY, entity.motionZ) + if entity.displayName ~= username then + hud_entities[entity.displayName] = (hud_entities[entity.displayName] or 0) + 1 + end + if entity.displayName ~= username and entity.displayName == entity.name and (math.floor(entity.yaw) ~= entity.yaw and math.floor(entity.pitch) ~= entity.pitch) then -- player, quite possibly + entity.v = entity.v + vector.new(0, gravity_motion_offset, 0) + table.insert(maybe_players, entity) + end + if entity.name == "plethora:laser" then + fast_mode_reqs.laser = true + end + if entity.name == "plethora:laser" and not addressed_lasers[entity.id] then + local closest_approach, param = project(entity.s, entity.v - user_meta.velocity, vector.new(0, 0, 0)) + if param > 0 and vector_sqlength(closest_approach) < 5 then + push_notice "Laser detected" + fast_mode_reqs.laser = true + local time_to_impact = (entity.s:length() / (entity.v - user_meta.velocity):length()) / 20 + print("got inbound laser", time_to_impact, vector_sqlength(closest_approach), param) + addressed_lasers[entity.id] = true + if gsettings.dodge then + schedule(function() + push_notice "Executing dodging" + local dir2d = vector.new(entity.motionX - user_meta.motionX, 0, entity.motionZ - user_meta.motionZ) + local perpendicular_dir2d = vector.new(1, 0, -dir2d.x / dir2d.z) + -- NaN contingency measures + if perpendicular_dir2d.x ~= perpendicular_dir2d.x or perpendicular_dir2d.z ~= perpendicular_dir2d.z then + perpendicular_dir2d = vector.new(-dir2d.z / dir2d.x, 0, 1) + end + if perpendicular_dir2d.x ~= perpendicular_dir2d.x or perpendicular_dir2d.z ~= perpendicular_dir2d.z then + local theta = math.random() * math.pi * 2 + perpendicular_dir2d = vector.new(math.cos(theta), 0, math.sin(theta)) + end + local y, p = calc_yaw_pitch(perpendicular_dir2d) + if math.random(1, 2) == 1 then p = -p end + ni.launch(y, p, 3) + end, math.max(0, time_to_impact / 2 - 0.1)) + end + schedule(function() addressed_lasers[entity.id] = false end, 15) + table.insert(lasers, entity) + end + end + end + for _, laser in pairs(lasers) do + for _, player in pairs(maybe_players) do + local closest_approach, param = project(laser.s, laser.v, player.s) + print(player.displayName, closest_approach, param) + if param < 0 and vector_sqlength(closest_approach - player.s) < 8 and gsettings.counter then + print("execute counterattack", player.displayName) + push_notice(("Counterattack %s"):format(player.displayName)) + targets[player.displayName:lower()] = "laser" + end + end + end + + render_hud() + + pcall(function() + group_3d.clear() + group_3d.recenter() + end) + + for _, entity in pairs(entities) do + local action = is_target(entity.displayName) + if action then + if action == "laser" then + schedule(function() lase(entity) end, 0, entity.id) + elseif action == "watch" then + schedule(function() ni.look(calc_yaw_pitch(entity.s)) end, 0, entity.id) + elseif action == "follow" then + schedule(function() + local y, p = calc_yaw_pitch(entity.s) + ni.launch(y, p, math.min(entity.s:length() / 24, 2)) + end, 0, entity.id) + end + fast_mode_reqs.acting = true + end + if gsettings.highlight and hud_entities[entity.displayName] and hud_entities[entity.displayName] < 20 then + local color = colortheme[action or "entity"] + local object = group_3d.addBox(entity.x - 0.25, entity.y - 0.25, entity.z - 0.25) + object.setColor(color) + object.setAlpha(128) + object.setDepthTested(false) + object.setSize(0.5, 0.5, 0.5) + if gsettings.ext_highlight then + local frame = group_3d.addFrame({entity.x - 0.25, entity.y + 0.25, entity.z - 0.25}) + frame.setDepthTested(false) + frame.addText({0, 0}, entity.displayName, nil, 3) + end + end + end + + local fast_mode = false + for _, m in pairs(fast_mode_reqs) do + fast_mode = fast_mode or m + end + + --status_lines.fast_mode = "Fast scan: " .. bool_to_yn(fast_mode) + + if fast_mode then sleep(0.1) else sleep(0.2) end + end +end + +local flight_shortcodes = { + o = "off", + b = "brake", + h = "hpower", + l = "lpower", + s = "std", + a = "align", + v = "hover" +} + +local flight_powers = { + std = 1, + lpower = 0.5, + hpower = 4, + align = 1, + hover = 1 +} + +local flight_target = nil + +local function xz_plane(v) + return vector.new(v.x, 0, v.z) +end + +-- As far as I can tell, a speed of more than 10 in the X/Z plane causes a reset of your velocity by the server and thus horrible rubberbanding. +local function maxvel_compensatory_launch(yaw, pitch, power) + local effective_power = (user_meta and user_meta.isElytraFlying) and (power * 0.4) or power + local impulse = direction_vector(yaw, pitch) * effective_power + local power_over_velocity_limit = math.max(xz_plane(user_meta.velocity + impulse):length() - 10, 0) + if user_meta and user_meta.isElytraFlying then + power = power - power_over_velocity_limit / 0.4 + else + power = power - power_over_velocity_limit + end + power = math.min(math.max(power, 0), 4) + if power > 0 then + ni.launch(yaw, pitch, power) + end +end + +local function run_flight() + if flight_shortcodes[gsettings.flight] then gsettings.flight = flight_shortcodes[gsettings.flight] end + local disp = gsettings.flight + if user_meta.deltaPosY < -0.3 and gsettings.brake then + ni.launch(0, 270, math.max(0.4, math.min(4, -user_meta.motionY / 1.5))) + --ni.launch(0, 270, 0.4) + --end + fast_mode_reqs.flying = true + disp = disp .. " F" + else + fast_mode_reqs.flying = false + end + if gsettings.flight == "std" or gsettings.flight == "hpower" or gsettings.flight == "lpower" or gsettings.flight == "align" or gsettings.flight == "hover" then + if user_meta.isElytraFlying or user_meta.isSneaking then + fast_mode_reqs.flying = true + end + if user_meta.isElytraFlying ~= user_meta.isSneaking then + if not user_meta.isAirborne and user_meta.pitch < -15 then + push_notice "Fast takeoff" + ni.launch(0, 270, 1) + end + local power = flight_powers[gsettings.flight] + if user_meta.isInWater then + power = power * 2 + end + local yaw, pitch = user_meta.yaw, user_meta.pitch + if pitch == 90 and gsettings.pitch_controls then + local y, p = calc_yaw_pitch(-user_meta.velocity) + ni.launch(y, p, math.min(user_meta.velocity:length(), 4)) + else + local raw_direction = direction_vector(yaw, pitch) + local impulse = vector.new(raw_direction.x, raw_direction.y * 1.5, raw_direction.z) * power + impulse = impulse + vector.new(0, 0.1, 0) + local y, p = calc_yaw_pitch(impulse) + maxvel_compensatory_launch(y, (gsettings.flight ~= "align" and p) or 10, math.min(4, impulse:length())) + end + end + elseif gsettings.flight == "brake" then + local y, p = calc_yaw_pitch(-user_meta.velocity) + ni.launch(y, p, math.min(user_meta.velocity:length(), 1)) + fast_mode_reqs.flying = true + end + status_lines.flight = "Flight: " .. disp +end + +local function ll_flight_control() + while true do + local ok, user_meta_temp + if ni.getMetaOwner then + ok, user_meta_temp = pcall(ni.getMetaOwner, username) + else + ok, user_meta_temp = pcall(ni.getMetaByName, username) + end + if not ok or not user_meta_temp then + speaker.playSound("entity.enderdragon.death") + user_meta = nil + for name, cfg in pairs(settings_cfg) do + if cfg.persist == false then + cfg.value = nil + end + end + work_queue = {} + ni = peripheral.wrap "back" + ni.canvas().clear() + error("Failed to fetch user metadata (assuming death): " .. tostring(user_meta_temp)) + end + user_meta = user_meta_temp + update_motion_vars(user_meta) + if user_meta.acceleration and gsettings.show_acceleration then + status_lines.acceleration = ("Acc: %.2f/%.2f"):format(user_meta.acceleration:length(), user_meta.acceleration.y) + end + + status_lines.vel = ("Vel: %.2f/%.2f"):format(user_meta.velocity:length(), user_meta.motionY) + + render_hud() + + local fast_mode = false + for _, m in pairs(fast_mode_reqs) do + fast_mode = fast_mode or m + end + + --status_lines.fast_mode = "Fast scan: " .. bool_to_yn(fast_mode) + + schedule(run_flight, 0, "flight") + + if not fast_mode then sleep(0.1) end + end +end + +local function queue_handler() + while true do + local init = os.clock() + for index, arg in pairs(work_queue) do + if arg[1] <= os.clock() then + arg[2]() + work_queue[index] = nil + end + end + if os.clock() == init then sleep() end + end +end + +local function estimate_tps() + while true do + local game_time_start = os.epoch "utc" + sleep(5) + local game_time_end = os.epoch "utc" + local utc_elapsed_seconds = (game_time_end - game_time_start) / 5000 + status_lines.tps = ("TPS: %.0f"):format(20 / utc_elapsed_seconds) + end +end + +local function within_epsilon(a, b) + return math.abs(a - b) < 1 +end + +-- TODO: unified navigation framework +local function fly_to_target() + local last_s, last_t + while true do + while not user_meta do sleep() end + if flight_target then + local x, y, z = gps.locate() + if not y then push_notice "GPS error" + else + if y < 256 then + ni.launch(0, 270, 4) + end + local position = vector.new(x, 0, z) + local curr_t = os.clock() + local displacement = flight_target - position + status_lines.flight_target = ("%d from %d %d"):format(displacement:length(), flight_target.x, flight_target.z) + local real_displacement = displacement + if last_t then + local delta_t = curr_t - last_t + local delta_s = displacement - last_s + local deriv = delta_s * (1/delta_t) + displacement = displacement + deriv + end + local pow = math.max(math.min(4, displacement:length() / 40), 0) + local yaw, pitch = calc_yaw_pitch(displacement) + maxvel_compensatory_launch(yaw, pitch, pow) + --sleep(0) + last_t = curr_t + last_s = real_displacement + if within_epsilon(position.x, flight_target.x) and within_epsilon(position.z, flight_target.z) then flight_target = nil end + end + else + status_lines.flight_target = nil + end + sleep(0.1) + end +end + +local function handle_commands() + while true do + local _, user, command, args = os.pullEvent "command" + if user == username then + if command == "lase" then + if args[1] then + targets[table.concat(args, " "):lower()] = "laser" + end + elseif command == "ctg" then + args[1] = args[1] or ".*" + local arg = table.concat(args, " ") + for k, v in pairs(targets) do + if k:lower():match(arg) then + chatbox.tell(user, k .. ": " .. v) + targets[k:lower()] = nil + end + end + elseif command == "watch" then + if args[1] then + targets[table.concat(args, " "):lower()] = "watch" + end + elseif command == "select" then + if args[1] then + targets[table.concat(args, " "):lower()] = "select" + end + elseif command == "follow" then + if args[1] then + targets[table.concat(args, " "):lower()] = "follow" + end + elseif command == "notice_test" then + push_notice(table.concat(args, " ")) + elseif command == "flyto" then + if args[1] == "cancel" or args[1] == nil then + flight_target = nil + else + local x, z = tonumber(args[1]), tonumber(args[2]) + if type(x) ~= "number" or type(z) ~= "number" then + chatbox.tell(user, "Usage: \\flyto x z") + else + flight_target = vector.new(x, 0, z) + end + end + elseif command == "update" then + local h, e = http.get "https://osmarks.net/stuff/ni-ctl.lua" + assert(h, "HTTP: " .. (e or "")) + local data = h.readAll() + h.close() + local file = fs.open(shell.getRunningProgram(), "w") + file.write(data) + file.close() + chatbox.tell(user, "Update updated.") + else + for key, cfg in pairs(settings_cfg) do + if key == command or cfg.shortcode == command or (cfg.alias and cfg.alias[command]) then + if cfg.type == "bool" then + if args[1] and (args[1]:match "y" or args[1]:match "t" or args[1]:match "on") then + gsettings[key] = true + elseif args[1] and (args[1]:match "f" or args[1]:match "^n") then + gsettings[key] = false + else + gsettings[key] = not gsettings[key] + end + chatbox.tell(user, ("%s: %s"):format(key, tostring(gsettings[key]))) + elseif cfg.type == "number" then + local value = tonumber(args[1]) + if not value then chatbox.tell(user, "Not a number") end + if cfg.max and value > cfg.max then chatbox.tell(user, ("Max is %d"):format(cfg.max)) end + if cfg.min and value < cfg.min then chatbox.tell(user, ("Max is %d"):format(cfg.min)) end + gsettings[key] = value + else + gsettings[key] = args[1] + end + break + end + end + end + end + end +end + +local function drill() + while true do + if gsettings.drill then + repeat sleep() until user_meta + if offload_laser then + offload_protocol("fire", user_meta.yaw, user_meta.pitch, gsettings.power) + else + schedule(function() repeat sleep() until user_meta ni.fire(user_meta.yaw, user_meta.pitch, gsettings.power) end, 0, "drill") + end + sleep(0.1) + else + os.pullEvent "settings_change" + end + end +end + +while true do + local cmds = {ll_flight_control, queue_handler, scan_entities, handle_commands, estimate_tps, fly_to_target, drill} + if spudnet_background then + table.insert(cmds, spudnet_background) + end + local ok, err = pcall(parallel.waitForAny, unpack(cmds)) + if err == "Terminated" then break end + printError(err) + sleep(0.2) +end diff --git a/computercraft/ni-ctl_spudnet_interface.lua b/computercraft/ni-ctl_spudnet_interface.lua new file mode 100644 index 0000000..28a4804 --- /dev/null +++ b/computercraft/ni-ctl_spudnet_interface.lua @@ -0,0 +1,88 @@ +local function spudnet() + local channel = settings.get "offload_channel" + + if not http or not http.websocket then return "Websockets do not actually exist on this platform" end + + local ws + local try_connect_loop, recv + + local function send_packet(msg) + local ok, err = pcall(ws.send, textutils.serialiseJSON(msg)) + if not ok then printError(err) try_connect_loop() end + end + + local function assert_ok(packet) + if packet.type == "error" then error(("%s %s %s"):format(packet["for"], packet.error, packet.detail)) end + end + + local function connect() + if ws then ws.close() end + local err + local url = "wss://spudnet.osmarks.net/v4?enc=json&rand=" .. math.random(0, 0xFFFFFFF) + ws, err = http.websocket(url) + if not ws then print("websocket failure %s", err) return false end + ws.url = url + + send_packet { type = "identify", implementation = "ni-ctl SPUDNET interface", key = settings.get "spudnet_key" } + assert_ok(recv()) + send_packet { type = "set_channels", channels = {channel} } + assert_ok(recv()) + return true + end + + recv = function() + while true do + local e, u, x, y = os.pullEvent() + if e == "websocket_message" and u == ws.url then + return textutils.unserialiseJSON(x) + elseif e == "websocket_closed" and u == ws.url then + printError(("websocket: %s/%d"):format(x, y or 0)) + end + end + end + + try_connect_loop = function() + while not connect() do + sleep(0.5) + end + end + + try_connect_loop() + + local function send(data) + send_packet { type = "send", channel = channel, data = data } + assert_ok(recv()) + end + + local ping_timeout_timer = nil + + local function ping_timer() + while true do + local _, t = os.pullEvent "timer" + if t == ping_timeout_timer and ping_timeout_timer then + -- 15 seconds since last ping, we probably got disconnected + print "SPUDNET timed out, attempting reconnect" + try_connect_loop() + end + end + end + + local function main() + while true do + local packet = recv() + if packet.type == "ping" then + send_packet { type = "pong", seq = packet.seq } + if ping_timeout_timer then os.cancelTimer(ping_timeout_timer) end + ping_timeout_timer = os.startTimer(15) + elseif packet.type == "error" then + print("SPUDNET error", packet["for"], packet.error, packet.detail, textutils.serialise(packet)) + elseif packet.type == "message" then + os.queueEvent("spudnet_message", packet.data, packet) + end + end + end + + return send, function() parallel.waitForAll(ping_timer, main) end +end + +return spudnet \ No newline at end of file diff --git a/computercraft/ni-position-approximator.lua b/computercraft/ni-position-approximator.lua new file mode 100644 index 0000000..b2fe0e7 --- /dev/null +++ b/computercraft/ni-position-approximator.lua @@ -0,0 +1,25 @@ +local iface = peripheral.wrap "back" +local x, y, z = gps.locate() + +local approx = y +local function do_approx() + local past_time = os.epoch "utc" + while true do + local time = os.epoch "utc" + local diff = (time - past_time) / 1000 + past_time = time + local meta = iface.getMetaByName "gollark" + approx = approx + (meta.motionY * diff) + sleep() + end +end + +local function do_gps() + while true do + local x, y, z = gps.locate() + print("real=", y, "\napprox=", approx, "\ndiff=", y - approx) + sleep(0.5) + end +end + +parallel.waitForAll(do_approx, do_gps) \ No newline at end of file diff --git a/computercraft/nilaser.lua b/computercraft/nilaser.lua new file mode 100644 index 0000000..ed98e2c --- /dev/null +++ b/computercraft/nilaser.lua @@ -0,0 +1,41 @@ +local ni = peripheral.find "neuralInterface" +if not ni or not ni.fire or not ni.sense then error "Neural interface with laser and entity sensor required!" end +local args = {...} +local power = table.remove(args, 1) +local num_power = tonumber(power) +if num_power then power = num_power end +local follow_mode, stare_mode = power == "follow", power == "stare" +local laser_mode = not (follow_mode or stare_mode) +if #args == 0 or not power then + error([[Usage: nilaser ]]) +end + +local function calc_yaw_pitch(x, y, z) + local pitch = -math.atan2(y, math.sqrt(x * x + z * z)) + local yaw = math.atan2(-x, z) + return math.deg(yaw), math.deg(pitch) +end + +local function is_target(entity) + for _, pattern in pairs(args) do + if entity.name:match(pattern) or entity.displayName:match(pattern) then return true end + end + return false +end + +while true do + for _, entity in pairs(ni.sense()) do + if is_target(entity) then + print(entity.name, entity.displayName) + local y, p = calc_yaw_pitch(entity.x, entity.y, entity.z) + if laser_mode then + ni.fire(y, p, power) + elseif follow_mode then + ni.launch(y, p, 1) + elseif stare_mode then + ni.look(y, p) + end + end + end + sleep(0.05) +end \ No newline at end of file diff --git a/computercraft/nnrelay.lua b/computercraft/nnrelay.lua new file mode 100644 index 0000000..7b697ad --- /dev/null +++ b/computercraft/nnrelay.lua @@ -0,0 +1,36 @@ +if require then + component = require "component" + computer = require "computer" +end + +computer.beep(200) + +--local net = component.proxy(component.list "internet"()) +local modem_id = component.list "modem"() +local modem = component.proxy(modem_id) + +modem.open(1025) -- command channel +modem.open(1024) -- nanobot reply channel + +while true do + local event = {computer.pullSignal()} + if event[1] == "modem_message" then + table.remove(event, 1) -- event + table.remove(event, 1) -- to + local from = table.remove(event, 1) -- from + local port = table.remove(event, 1) + local distance = table.remove(event, 1) -- distance + if print then print(port, distance) end + if distance < 16 then + if port == 1024 then + --computer.beep(800) + modem.broadcast(1026, from, table.unpack(event)) + elseif port == 1025 then + --computer.beep(1600) + --table.remove(event) -- remove last two arguments added when transmitting + table.remove(event) + modem.broadcast(1024, table.unpack(event)) + end + end + end +end \ No newline at end of file diff --git a/computercraft/number-display.lua b/computercraft/number-display.lua new file mode 100644 index 0000000..637d597 --- /dev/null +++ b/computercraft/number-display.lua @@ -0,0 +1,90 @@ +local screens = {peripheral.find "monitor"} + +local p = {} +local permutation = {151,160,137,91,90,15, + 131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23, + 190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33, + 88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166, + 77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244, + 102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196, + 135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123, + 5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42, + 223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9, + 129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228, + 251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107, + 49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254, + 138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180 +} + +for i = 0, 255 do + p[i] = permutation[i + 1] + p[256 + i] = permutation[i + 1] +end + +local function fade(t) + return t * t * t * (t * (t * 6 - 15) + 10) +end + +local function lerp(t, a, b) + return a + t * (b - a) +end + +local function grad(hash, x, y, z) + local h, u, v = hash % 16 + if h < 8 then u = x else u = y end + if h < 4 then v = y elseif h == 12 or h == 14 then v = x else v = z end + local r + if h % 2 == 0 then r = u else r = -u end + if h % 4 == 0 then r = r + v else r = r - v end + return r +end + +local function perlin(x, y, z) + y = y or 0 + z = z or 0 + local X = math.floor(x % 255) + local Y = math.floor(y % 255) + local Z = math.floor(z % 255) + x = x - math.floor(x) + y = y - math.floor(y) + z = z - math.floor(z) + local u = fade(x) + local v = fade(y) + local w = fade(z) + + A = p[X ] + Y + AA = p[A ] + Z + AB = p[A + 1] + Z + B = p[X + 1] + Y + BA = p[B ] + Z + BB = p[B + 1] + Z + + return lerp(w, lerp(v, lerp(u, grad(p[AA ], x , y , z ), + grad(p[BA ], x - 1, y , z )), + lerp(u, grad(p[AB ], x , y - 1, z ), + grad(p[BB ], x - 1, y - 1, z ))), + lerp(v, lerp(u, grad(p[AA + 1], x , y , z - 1), + grad(p[BA + 1], x - 1, y , z - 1)), + lerp(u, grad(p[AB + 1], x , y - 1, z - 1), + grad(p[BB + 1], x - 1, y - 1, z - 1)))) +end + +while true do + for _, screen in pairs(screens) do + screen.clear() + local w, h = screen.getSize() + for x = 1, w, 2 do + for y = 1, h, 2 do + screen.setCursorPos(x, y) + if math.random(0, 50) == 0 then + screen.setTextColor(2^math.random(2, 14)) + else + screen.setTextColor(colors.white) + end + local pl = (math.max(math.min(perlin(x * 4.306, y * 4.306, os.clock() * 0.05) * 3, 1), -1) + 1) * 4.5 + screen.write(tostring(math.floor(pl))) + end + end + end + sleep(1) +end \ No newline at end of file diff --git a/computercraft/oc-drone-netboot.lua b/computercraft/oc-drone-netboot.lua new file mode 100644 index 0000000..0291ec4 --- /dev/null +++ b/computercraft/oc-drone-netboot.lua @@ -0,0 +1,19 @@ +local net = component.proxy(component.list "internet"()) +local eeprom = component.proxy(component.list "eeprom"()) + +local function fetch(url) + local res, err = net.request(url) + if not res then error(url .. " error: " .. err) end + local out = {} + while true do + local chunk, err = res.read() + if err then error(url .. " error: " .. err) end + if chunk then table.insert(out, chunk) + else return table.concat(out) end + end +end + +local eepromdata = eeprom.getData() +if eepromdata == "" then error "No URL loaded" end +local fn = assert(load(fetch(eepromdata))) +fn() \ No newline at end of file diff --git a/computercraft/oc-drone.lua b/computercraft/oc-drone.lua new file mode 100644 index 0000000..762fa0a --- /dev/null +++ b/computercraft/oc-drone.lua @@ -0,0 +1,232 @@ +local drone = component.proxy(component.list "drone"()) +local net = component.proxy(component.list "internet"()) +local wlan = component.proxy(component.list "modem"()) +local comp = component.proxy(component.list "computer"()) +wlan.setWakeMessage("poweron", true) + +local central_point = { x = 297, y = 80, z = 294 } + +local statuses = { + loading = { text = "LOAD", color = 0x000000 }, + moving = { text = "GO", color = 0xFFFF00 }, + idle = { text = "IDLE", color = 0x00FFFF }, + error = { text = "ERROR", color = 0xFF0000 }, + low_battery = { text = "ELOW", 0xFF8800 } +} + +local function set_status(status) + local stat = statuses[status] + drone.setLightColor(stat.color or 0xFFFFFF) + drone.setStatusText((stat.text or status) .. (" "):rep(8)) +end + +set_status "loading" +comp.beep(600, 1) + +local function energy() + return computer.energy() / computer.maxEnergy() +end + +local GPS_PING_CHANNEL, GPS_RESPONSE_CHANNEL, TIMEOUT = 2048, 2047, 1 + +wlan.setStrength(math.huge) + +local function fetch(url) + local res, err = net.request(url) + if not res then error(url .. " error: " .. err) end + local out = {} + while true do + local chunk, err = res.read() + if err then error(url .. " error: " .. err) end + if chunk then table.insert(out, chunk) + else return table.concat(out) end + end +end + +local function round(v, m) + m = m or 1.0 + return { + x = math.floor((v.x+(m*0.5))/m)*m, + y = math.floor((v.y+(m*0.5))/m)*m, + z = math.floor((v.z+(m*0.5))/m)*m + } +end + +local function len(v) + return math.sqrt(v.x^2 + v.y^2 + v.z^2) +end + +local function cross(v, b) + return {x = v.y*b.z-v.z*b.y, y = v.z*b.x-v.x*b.z, z = v.x*b.y-v.y*b.x} +end + +local function dot(v, b) + return v.x*b.x + v.y*b.y + v.z*b.z +end + +local function add(v, b) + return {x = v.x+b.x, y = v.y+b.y, z = v.z+b.z} +end + +local function sub(v, b) + return {x = v.x-b.x, y = v.y-b.y, z = v.z-b.z} +end + +local function mul(v, m) + return {x = v.x*m, y = v.y*m, z = v.z*m} +end + +local function norm(v) + return mul(v, 1/len(v)) +end + +local function trilaterate(A, B, C) + local a2b = {x = B.x-A.x, y = B.y-A.y, z = B.z-A.z} + local a2c = {x = C.x-A.x, y = C.y-A.y, z = C.z-A.z} + if math.abs(dot(norm(a2b), norm(a2c))) > 0.999 then + return nil + end + local d = len(a2b) + local ex = norm(a2b) + local i = dot(ex, a2c) + local ey = norm(sub(mul(ex, i), a2c)) + local j = dot(ey, a2c) + local ez = cross(ex, ey) + local r1 = A.d + local r2 = B.d + local r3 = C.d + local x = (r1^2 - r2^2 + d^2) / (2*d) + local y = (r1^2 - r3^2 - x^2 + (x-i)^2 + j^2) / (2*j) + local result = add(A, add(mul(ex, x), mul(ey, y))) + local zSquared = r1^2 - x^2 - y^2 + if zSquared > 0 then + local z = math.sqrt(zSquared) + local result1 = add(result, mul(ez, z)) + local result2 = add(result, mul(ez, z)) + local rounded1, rounded2 = round(result1, 0.01), round(result2, 0.01) + if rounded1.x ~= rounded2.x or + rounded1.y ~= rounded2.y or + rounded1.z ~= rounded2.z then + return rounded1, rounded2 + else + return rounded1 + end + end + return round(result, 0.01) +end + +local function narrow(p1, p2, fix) + local dist1 = math.abs(len(sub(p1, fix)) - fix.d) + local dist2 = math.abs(len(sub(p2, fix)) - fix.d) + if math.abs(dist1 - dist2) < 0.01 then + return p1, p2 + elseif dist1 < dist2 then + return round(p1, 0.01) + else + return round(p2, 0.01) + end +end + +local function locate(timeout) + local timeout = timeout or TIMEOUT + wlan.open(GPS_RESPONSE_CHANNEL) + wlan.broadcast(GPS_PING_CHANNEL, "PING") + local fixes = {} + local pos1, pos2 = nil, nil + local deadline = computer.uptime() + timeout + local dim + repeat + local event, _, from, port, distance, x, y, z, dimension = computer.pullSignal(deadline - computer.uptime()) + if event == "modem_message" and port == GPS_RESPONSE_CHANNEL and x and y and z then + if type(dim) == "string" then dim = dimension end + local fix = {x = x, y = y, z = z, d = distance} + if fix.d == 0 then + pos1, pos2 = {fix.x, fix.y, fix.z}, nil + else + table.insert(fixes, fix) + if #fixes >= 3 then + if not pos1 then + pos1, pos2 = trilaterate(fixes[1], fixes[2], fixes[#fixes]) + else + pos1, pos2 = narrow(pos1, pos2, fixes[#fixes]) + end + end + end + if pos1 and not pos2 then + break + end + end + until computer.uptime() >= deadline + wlan.close(GPS_RESPONSE_CHANNEL) + if pos1 and pos2 then + return nil + elseif pos1 then + return pos1, dim + else + return nil + end +end + +local a={["\\"]="\\\\",["\""]="\\\"",["\b"]="\\b",["\f"]="\\f",["\n"]="\\n",["\r"]="\\r",["\t"]="\\t"}local b={["\\/"]="/"}for c,d in pairs(a)do b[d]=c end;local e;local function f(...)local g={}for h=1,select("#",...)do g[select(h,...)]=true end;return g end;local i=f(" ","\t","\r","\n")local j=f(" ","\t","\r","\n","]","}",",")local k=f("\\","/",'"',"b","f","n","r","t","u")local l=f("true","false","null")local m={["true"]=true,["false"]=false,["null"]=nil}local function n(o,p,q,r)for h=p,#o do if q[o:sub(h,h)]~=r then return h end end;return#o+1 end;local function s(o,p,t)local u=1;local v=1;for h=1,p-1 do v=v+1;if o:sub(h,h)=="\n"then u=u+1;v=1 end end;error(string.format("%s at line %d col %d",t,u,v))end;local function w(x)local y=math.floor;if x<=0x7f then return string.char(x)elseif x<=0x7ff then return string.char(y(x/64)+192,x%64+128)elseif x<=0xffff then return string.char(y(x/4096)+224,y(x%4096/64)+128,x%64+128)elseif x<=0x10ffff then return string.char(y(x/262144)+240,y(x%262144/4096)+128,y(x%4096/64)+128,x%64+128)end;error(string.format("invalid unicode codepoint '%x'",x))end;local function z(A)local B=tonumber(A:sub(3,6),16)local C=tonumber(A:sub(9,12),16)if C then return w((B-0xd800)*0x400+C-0xdc00+0x10000)else return w(B)end end;local function D(o,h)local E=false;local F=false;local G=false;local H;for I=h+1,#o do local J=o:byte(I)if J<32 then s(o,I,"control character in string")end;if H==92 then if J==117 then local K=o:sub(I+1,I+5)if not K:find("%x%x%x%x")then s(o,I,"invalid unicode escape in string")end;if K:find("^[dD][89aAbB]")then F=true else E=true end else local L=string.char(J)if not k[L]then s(o,I,"invalid escape char '"..L.."' in string")end;G=true end;H=nil elseif J==34 then local A=o:sub(h+1,I-1)if F then A=A:gsub("\\u[dD][89aAbB]..\\u....",z)end;if E then A=A:gsub("\\u....",z)end;if G then A=A:gsub("\\.",b)end;return A,I+1 else H=J end end;s(o,h,"expected closing quote for string")end;local function M(o,h)local J=n(o,h,j)local A=o:sub(h,J-1)local x=tonumber(A)if not x then s(o,h,"invalid number '"..A.."'")end;return x,J end;local function N(o,h)local J=n(o,h,j)local O=o:sub(h,J-1)if not l[O]then s(o,h,"invalid literal '"..O.."'")end;return m[O],J end;local function P(o,h)local g={}local x=1;h=h+1;while 1 do local J;h=n(o,h,i,true)if o:sub(h,h)=="]"then h=h+1;break end;J,h=e(o,h)g[x]=J;x=x+1;h=n(o,h,i,true)local Q=o:sub(h,h)h=h+1;if Q=="]"then break end;if Q~=","then s(o,h,"expected ']' or ','")end end;return g,h end;local function R(o,h)local g={}h=h+1;while 1 do local S,T;h=n(o,h,i,true)if o:sub(h,h)=="}"then h=h+1;break end;if o:sub(h,h)~='"'then s(o,h,"expected string for key")end;S,h=e(o,h)h=n(o,h,i,true)if o:sub(h,h)~=":"then s(o,h,"expected ':' after key")end;h=n(o,h+1,i,true)T,h=e(o,h)g[S]=T;h=n(o,h,i,true)local Q=o:sub(h,h)h=h+1;if Q=="}"then break end;if Q~=","then s(o,h,"expected '}' or ','")end end;return g,h end;local U={['"']=D,["0"]=M,["1"]=M,["2"]=M,["3"]=M,["4"]=M,["5"]=M,["6"]=M,["7"]=M,["8"]=M,["9"]=M,["-"]=M,["t"]=N,["f"]=N,["n"]=N,["["]=P,["{"]=R}e=function(o,p)local Q=o:sub(p,p)local y=U[Q]if y then return y(o,p)end;s(o,p,"unexpected character '"..Q.."'")end;local function json_decode(o)if type(o)~="string"then error("expected argument of type string, got "..type(o))end;local g,p=e(o,n(o,1,i,true))p=n(o,p,i,true)if p<=#o then s(o,p,"trailing garbage")end;return g end + +local function sleep(timeout) + local deadline = computer.uptime() + (timeout or 0) + repeat + computer.pullSignal(deadline - computer.uptime()) + until computer.uptime() >= deadline +end + +local function moveraw(x, y, z) + set_status "moving" + drone.move(x, y, z) + repeat + sleep(0.5) + until drone.getVelocity() < 0.1 + set_status "idle" +end + +local function move(pos) + moveraw(0, 32, 0) + moveraw(pos.x, pos.y, pos.z) + moveraw(0, -32, 0) +end + +local function follow(currpos) + local data = json_decode(fetch "https://dynmap.codersnet.pw/up/world/world/1588704574112") + local possibles = {} + for _, p in pairs(data.players) do + local plrpos = { x = p.x, y = p.y, z = p.z } + local dist = len(sub(plrpos, central_point)) + if dist < 100 and p.world == "world" then + table.insert(possibles, plrpos) + end + end + if #possibles == 0 then return false, "TNF" end + local targpos = possibles[math.random(1, #possibles)] + local offset = sub(targpos, currpos) + comp.beep(400, 0.5) + move(offset) + return true +end + +while true do + set_status "idle" + local currpos = locate() + if currpos then + wlan.broadcast(1033, drone.name(), "LOC", currpos.x, currpos.y, currpos.z) + local offset_from_hub = sub(central_point, currpos) + if len(offset_from_hub) then + move(offset_from_hub) + else + local ok, err = follow(currpos) + if not ok then set_status "error" drone.setStatusText("E" .. err) sleep(10) end + sleep(1) + end + end + if energy() < 0.3 then + wlan.broadcast(1033, drone.name(), "LOW") + comp.beep(2000, 1.5) + status "low_battery" + move(sub(central_point, locate())) + end +end \ No newline at end of file diff --git a/computercraft/oc-gps.lua b/computercraft/oc-gps.lua new file mode 100644 index 0000000..30a3da3 --- /dev/null +++ b/computercraft/oc-gps.lua @@ -0,0 +1,65 @@ +local eeprom = component.proxy(component.list "eeprom"()) +local wlan = component.proxy(component.list "modem"()) +local comp = component.proxy(component.list "computer"()) + +local function serialize(a)local b=type(a)if b=="number"then return tostring(a)elseif b=="string"then return ("%q"):format(a)elseif b=="table"then local c="{"for d,e in pairs(a)do c=c..string.format("[%s]=%s,",serialize(d),serialize(e))end;return c.."}"elseif b=="boolean"then return tostring(a)else return("%q"):format(tostring(a))end end +local function unserialize(a) local fn, e = load("return "..a:gsub("functio".."n", ""), "@deser", "t", {}) if not fn then return false, e end return fn() end +local a=string.byte;local function crc32(c)local d,e,f;e=0xffffffff;for g=1,#c do d=a(c,g)e=e~d;for h=1,8 do f=-(e&1);e=(e>>1)~(0xedb88320&f)end end;return(~e)&0xffffffff end +local conf, e = unserialize(eeprom.getData()) +if e then error("Config parse error: " .. e) end + +wlan.open(2048) +wlan.open(2049) + +local function respond(main, auth) + local data, e = unserialize(main) + if not data then error("unserialization: " .. e) end + if type(data) ~= "table" then error "command format invalid" end + local authed = false + if data.time and auth then + local timediff = math.abs(os.time() - data.time) + if timediff > 1000 then error "tdiff too high" end + local vauth = crc32(main .. conf.psk) + if auth ~= vauth then error "auth invalid" end + authed = true + end + local ctype = data[1] + if ctype == "ping" then return conf.uid end + if authed then + if ctype == "reflash" and data[2] then + eeprom.set(data[2]) + for i = 1, 5 do + comp.beep(800, 0.2) + comp.beep(1200, 0.2) + end + return #data[2] + elseif ctype == "setpos" and data[2] and data[3] then + if data[2] == conf.uid then + conf.pos = data[3] + eeprom.setData(serialize(conf)) + eeprom.setLabel("GPS"..conf.uid) + return true + end + return "ignoring" + end + end + error("invalid command (auth: " .. tostring(authed) .. ")") +end + +while true do + local ev, _, from, port, distance, m1, m2 = computer.pullSignal() + if ev == "modem_message" then + if port == 2048 and m1 == "PING" then + if conf.pos then + wlan.broadcast(2047, table.unpack(conf.pos)) + else + comp.beep(400, 2) + end + elseif port == 2049 and distance < 8 then + comp.beep(1000, 0.5) + local ok, res = pcall(respond, m1, m2) + wlan.broadcast(2050, conf.uid, ok, serialize(res)) + if not ok then comp.beep(1500, 2) end + end + end +end \ No newline at end of file diff --git a/computercraft/oc-remote-wake.lua b/computercraft/oc-remote-wake.lua new file mode 100644 index 0000000..f29d054 --- /dev/null +++ b/computercraft/oc-remote-wake.lua @@ -0,0 +1,32 @@ +local wlan = component.proxy(component.list "modem"()) +local computer_peripheral = component.proxy(component.list "computer"()) +wlan.setWakeMessage("poweron", true) + +local function sleep(timeout) + local deadline = computer.uptime() + (timeout or 0) + repeat + computer.pullSignal(deadline - computer.uptime()) + until computer.uptime() >= deadline +end + +local function try_power_on(comp) + local p = component.proxy(comp) + if p.isOn and p.turnOn then + if not p.isOn() then + p.turnOn() + computer_peripheral.beep(440) + end + end + if p.isRunning and p.start then + if not p.isRunning() then + p.start() + computer_peripheral.beep(800) + end + end +end + +while true do + for addr in component.list "turtle" do try_power_on(addr) end + for addr in component.list "computer" do try_power_on(addr) end + sleep(1) +end \ No newline at end of file diff --git a/computercraft/oc-robot-name-thing.lua b/computercraft/oc-robot-name-thing.lua new file mode 100644 index 0000000..694ba45 --- /dev/null +++ b/computercraft/oc-robot-name-thing.lua @@ -0,0 +1,19 @@ +local h = http.get "https://raw.githubusercontent.com/MightyPirates/OpenComputers/master-MC1.7.10/src/main/resources/assets/opencomputers/robot.names" +local name_list = h.readAll() +h.close() + +local names = {} + +local regex = "([^\n]+)" -- technically a pattern and not regex +for line in name_list:gmatch(regex) do + local comment_pos = line:find "#" + if comment_pos then line = line:sub(1, comment_pos - 1) end + local line = line:gsub(" *$", "") + if #line > 0 then + table.insert(names, line) + end +end + +local name = names[math.random(1, #names)] +print(name) +os.setComputerLabel(name) diff --git a/computercraft/one-way-hallway.lua b/computercraft/one-way-hallway.lua new file mode 100644 index 0000000..32d3262 --- /dev/null +++ b/computercraft/one-way-hallway.lua @@ -0,0 +1,62 @@ +local integrators = {} +local sensor = peripheral.find "plethora:sensor" +for i = 993, 996 do + table.insert(integrators, peripheral.wrap("redstone_integrator_" .. i)) +end +local min_bb = vector.new(-7, -4, -9999999) +local max_bb = vector.new(3, 0, 9999999) +local entry_sides = {} + +local function set_open(state) + for _, i in pairs(integrators) do + i.setOutput("up", state) + i.setOutput("south", state) + end +end + +local function is_bounded_by(min, max, v) + return min.x <= v.x and max.x >= v.x and min.y <= v.y and max.y >= v.y and min.z <= v.z and max.z >= v.z +end + +local function scan() + local nearby = sensor.sense() + local any = false + local ret = {} + for k, v in pairs(nearby) do + v.s = vector.new(v.x, v.y, v.z) + v.v = vector.new(v.motionX, v.motionY, v.motionZ) + v.distance = v.s:length() + if v.displayName == v.name then + if is_bounded_by(min_bb, max_bb, v.s) then table.insert(ret, v) end + any = true + end + end + return ret, any +end + + +while true do + local es, run_fast = scan() + local closed = false + local seen = {} + for _, e in pairs(es) do + if entry_sides[e.name] == nil then + entry_sides[e.name] = e.s.z > 0 -- true if on "closed" side + end + seen[e.name] = true + end + for _, entered_via_closed_side in pairs(entry_sides) do + if entered_via_closed_side then + closed = true + end + end + set_open(not closed) + for k, v in pairs(entry_sides) do + print(os.clock(), k, v) + if not seen[k] then + entry_sides[k] = nil + end + end + set_open(not closed) + if not run_fast then sleep(0.1) end +end \ No newline at end of file diff --git a/computercraft/opus-trilaterator.lua b/computercraft/opus-trilaterator.lua new file mode 100644 index 0000000..98be4f6 --- /dev/null +++ b/computercraft/opus-trilaterator.lua @@ -0,0 +1,102 @@ +-- TrilateratorGPS, modified a bit to track Opus SNMP pings instead now that GPS is anonymized + +local filter = ... + +local config = dofile "config.lua" +local modems = {} +for name, location in pairs(config.modems) do + modems[name] = peripheral.wrap(name) + modems[name].location = location + modems[name].open(999) +end + +local function timestamp() + return os.date "!%X" +end + +-- Trilateration code from GPS and modified slightly + +local function trilaterate( A, B, C ) + local a2b = B.position - A.position + local a2c = C.position - A.position + + if math.abs( a2b:normalize():dot( a2c:normalize() ) ) > 0.999 then + return nil + end + + local d = a2b:length() + local ex = a2b:normalize( ) + local i = ex:dot( a2c ) + local ey = (a2c - (ex * i)):normalize() + local j = ey:dot( a2c ) + local ez = ex:cross( ey ) + + local r1 = A.distance + local r2 = B.distance + local r3 = C.distance + + local x = (r1*r1 - r2*r2 + d*d) / (2*d) + local y = (r1*r1 - r3*r3 - x*x + (x-i)*(x-i) + j*j) / (2*j) + + local result = A.position + (ex * x) + (ey * y) + + local zSquared = r1*r1 - x*x - y*y + if zSquared > 0 then + local z = math.sqrt( zSquared ) + local result1 = result + (ez * z) + local result2 = result - (ez * z) + + local rounded1, rounded2 = result1:round( 0.01 ), result2:round( 0.01 ) + if rounded1.x ~= rounded2.x or rounded1.y ~= rounded2.y or rounded1.z ~= rounded2.z then + return rounded1, rounded2 + else + return rounded1 + end + end + return result:round( 0.01 ) +end + +local function narrow( p1, p2, fix ) + local dist1 = math.abs( (p1 - fix.position):length() - fix.distance ) + local dist2 = math.abs( (p2 - fix.position):length() - fix.distance ) + + if math.abs(dist1 - dist2) < 0.01 then + return p1, p2 + elseif dist1 < dist2 then + return p1:round( 0.01 ) + else + return p2:round( 0.01 ) + end +end + +local monitor = peripheral.find "monitor" +if monitor then + monitor.setTextScale(0.5) + term.redirect(monitor) +end + +print(timestamp(), "Initialized") + +local fixes = {} + +while true do + local _, modem, channel, reply_channel, message, distance = os.pullEvent "modem_message" + if distance then + if not fixes[reply_channel] then fixes[reply_channel] = {} end + local rc_fixes = fixes[reply_channel] + local recv_modem = modems[modem] + table.insert(rc_fixes, { position = vector.new(unpack(recv_modem.location)), distance = distance }) + if #rc_fixes == 4 then + local p1, p2 = trilaterate(rc_fixes[1], rc_fixes[2], rc_fixes[3]) + if p1 and p2 then + local pos = narrow(p1, p2, rc_fixes[4]) + local status, label = "?", "?" + if type(message) == "table" then status = tostring(message.status) label = tostring(message.label) end + if (not filter) or (label:match(filter)) then + print(timestamp(), ("%05d %s (%.0f %.0f %.0f) %s"):format(reply_channel, label, pos.x, pos.y, pos.z, status)) + end + end + fixes[reply_channel] = {} + end + end +end \ No newline at end of file diff --git a/computercraft/ore-thing.lua b/computercraft/ore-thing.lua new file mode 100644 index 0000000..02361a4 --- /dev/null +++ b/computercraft/ore-thing.lua @@ -0,0 +1,19 @@ +local m = peripheral.find("modem", function(_, o) return o.isWireless() end) + +local function send_metric(...) + m.transmit(3054, 3054, {...}) +end + +function pulse(side) + rs.setOutput(side,true) + sleep(0.05) + rs.setOutput(side,false) + sleep(0.05) +end + +while true do + local exists, data = turtle.inspect() + if not exists then sleep(0.05) + elseif data.name == "minecraft:netherrack" or data.name == "minecraft:stone" then pulse "left" + else send_metric("ores_made", "quantity of ores summoned from beespace", "inc", 1) pulse "right" end +end \ No newline at end of file diff --git a/computercraft/p2p-manager.lua b/computercraft/p2p-manager.lua new file mode 100644 index 0000000..77aaae4 --- /dev/null +++ b/computercraft/p2p-manager.lua @@ -0,0 +1,97 @@ +local data +local file = "./p2p.tbl" + +local function save() + local f = fs.open(file, "w") + f.write(textutils.serialise(data)) + f.close() +end + +local function load() + if fs.exists(file) then + local f = fs.open(file, "r") + local x = textutils.unserialise(f.readAll()) + f.close() + return x + end +end + +local function split(str) + local t = {} + for w in str:gmatch("%S+") do table.insert(t, w) end + return t +end + +data = load() or {} + +local case_insensitive = { + __index = function( table, key ) + local value = rawget( table, key ) + if value ~= nil then + return value + end + if type(key) == "string" then + local value = rawget( table, string.lower(key) ) + if value ~= nil then + return value + end + end + return nil + end +} + +setmetatable(data, case_insensitive) + +local function tunnel_info(name) + local d = data[name] + return ("%s: %d %s"):format(name, d.channels, d.description) +end + +local commands = { + list = function() + for t in pairs(data) do print(tunnel_info(t)) end + end, + info = function(name) + print(tunnel_info(name)) + end, + describe = function(name) + local t = data[name] + print("Description:", t.description) + write "New description: " + t.description = read() + end, + add = function(name) + data[name] = { + channels = 0, + description = "None set." + } + end, + channels = function(name, by) + local by = tonumber(by) + if not by then error "Invalid number!" end + local t = data[name] + print("Channels:", t.channels) + print("Increasing by:", by) + t.channels = t.channels + by + print("New channels:", t.channels) + end, + delete = function(name) + data[name] = nil + end +} + +setmetatable(commands, case_insensitive) + +local hist = {} + +while true do + write "> " + local text = read(nil, hist) + table.insert(hist, text) + local tokens = split(text) + local command = table.remove(tokens, 1) + + local ok, err = pcall(commands[command], unpack(tokens)) + save() + if not ok then printError(err) end +end \ No newline at end of file diff --git a/computercraft/panel-turtle.lua b/computercraft/panel-turtle.lua new file mode 100644 index 0000000..b1c8a6f --- /dev/null +++ b/computercraft/panel-turtle.lua @@ -0,0 +1,104 @@ +local sensor = peripheral.wrap "right" +local laser = peripheral.wrap "left" +local modem = peripheral.find "modem" + +local targets = {} + +local function scan() + local nearby = sensor.sense() + local ret = {} + for k, v in pairs(nearby) do + v.s = vector.new(v.x, v.y, v.z) + v.v = vector.new(v.motionX, v.motionY, v.motionZ) + v.distance = v.s:length() + if v.displayName == v.name then ret[v.name] = v end + end + return ret +end + +local function calc_yaw_pitch(v) + local x, y, z = v.x, v.y, v.z + local pitch = -math.atan2(y, math.sqrt(x * x + z * z)) + local yaw = math.atan2(-x, z) + return math.deg(yaw), math.deg(pitch) +end + +local function vector_sqlength(self) + return self.x * self.x + self.y * self.y + self.z * self.z +end + +local function project(line_start, line_dir, point) + local t = (point - line_start):dot(line_dir) / vector_sqlength(line_dir) + return line_start + line_dir * t, t +end + +local function angles_to_axis(yaw, pitch) + return vector.new( + -math.sin(math.rad(yaw)) * math.cos(math.rad(pitch)), + -math.sin(math.rad(pitch)), + math.cos(math.rad(yaw)) * math.cos(math.rad(pitch)) + ) +end + +local laser_origin = vector.new(0, 0, 0) + +local function would_hit(beam_line, safe_position, target_position) + local point, t = project(laser_origin, beam_line, safe_position) + return t > 0 and (point - safe_position):length() < 1.5 and safe_position:length() < target_position:length() +end + +local function lase(entity, others) + local target_location = entity.s - vector.new(0, 1, 0) + for i = 1, 5 do + target_location = entity.s + entity.v * (target_location:length() / 1.5) + end + local y, p = calc_yaw_pitch(target_location) + local line = angles_to_axis(y, p) + for _, other in pairs(others) do + if would_hit(line, other.s, target_location) then + --print("would hit", other.name) + return false + end + end + if would_hit(line, vector.new(-3, 0, 2), target_location) then + return false + end + print(y) + if y > (-90 + 45) or y < (-90 - 45) then + return false + end + rs.setOutput("front", true) + laser.fire(y, p, 1) + sleep(0.05) + rs.setOutput("front", false) +end + +local function laser_defense() + while true do + local entities = scan() + local now = os.epoch "utc" + local action_taken = false + for _, entity in pairs(entities) do + local targeted_at = targets[entity.name] + if targeted_at and targeted_at > (now - 60000) then + print("lasing", entity.displayName, entity.s) + lase(entity, entities) + action_taken = true + end + end + if not action_taken then sleep(0.2) end + end +end + +local function laser_commands() + modem.open(55) + while true do + local _, _, c, rc, m = os.pullEvent "modem_message" + if c == 55 and type(m) == "table" and m[1] == "lase" and type(m[2]) == "string" then + targets[m[2]] = os.epoch "utc" + print("command to lase", m[2], "remotely") + end + end +end + +parallel.waitForAll(laser_defense, laser_commands) \ No newline at end of file diff --git a/computercraft/pgps.lua b/computercraft/pgps.lua new file mode 100644 index 0000000..71bb98e --- /dev/null +++ b/computercraft/pgps.lua @@ -0,0 +1,327 @@ +CHANNEL_GPS = 65534 +CHANNEL_SNMP = 999 + +local function trilaterate(A, B, C) + local a2b = B.pos - A.pos + local a2c = C.pos - A.pos + + if math.abs(a2b:normalize():dot(a2c:normalize())) > 0.999 then + return nil + end + + local d = a2b:length() + local ex = a2b:normalize() + local i = ex:dot(a2c) + local ey = (a2c - ex * i):normalize() + local j = ey:dot(a2c) + local ez = ex:cross(ey) + + local r1 = A.distance + local r2 = B.distance + local r3 = C.distance + + local x = (r1 * r1 - r2 * r2 + d * d) / (2 * d) + local y = (r1 * r1 - r3 * r3 - x * x + (x - i) * (x - i) + j * j) / (2 * j) + + local result = A.pos + ex * x + ey * y + + local zSquared = r1 * r1 - x * x - y * y + if zSquared > 0 then + local z = math.sqrt(zSquared) + local result1 = result + ez * z + local result2 = result - ez * z + + return result1, result2 + end + return result +end + +-- from Opus somewhere +local function permutation(tbl, n) + local function permgen(a, n) + if n == 0 then + coroutine.yield(a) + else + for i=1,n do + a[n], a[i] = a[i], a[n] + permgen(a, n - 1) + a[n], a[i] = a[i], a[n] + end + end + end + local co = coroutine.create(function() permgen(tbl, n) end) + return function() + local _, res = coroutine.resume(co) + return res + end +end + +local known_opus_devices = { + ["6_4_milo_2"] = { -3182, 62, -5125, dimension = "overworld" }, + ["NationalCenterForMissingTurtles #1"] = { -3174, 73, -5124, dimension = "overworld" }, + ["computer_19171"] = { -3178, 64, -5130, dimension = "overworld" }, + ["RangerStore"] = { 217, 72, 123, dimension = "overworld" }, + ["AlexMilo"] = { -1785, 50, -2759, dimension = "overworld" }, + ["Solar-newmilo"] = { -3073, 78, -3008, dimension = "overworld" }, + ["ScorchMilo"] = { 269, 58, 421, dimension = "overworld" }, + ["scorchsfurninator"] = { 269, 54, 421, dimension = "overworld" }, + ["OMGFurni"] = { 4874, 76, -1701, dimension = "overworld" }, + ["computer_21867"] = { -7118, 52, -7354, dimension = "overworld" }, + ["LeClercMilo"] = { -7116, 50, -7357, dimension = "overworld" }, + ["manager_of_ground_trap"] = { 291, 31, -11, dimension = "overworld" }, + ["CodedPythonMilo"] = { -2597, 65, 4998, dimension = "overworld" }, + ["DistantMilo2"] = { -417, 80, -3049, dimension = "overworld" }, + ["NationalCenterForMissingTurtles #2"] = { 6, 35, -41, dimension = "nether" }, + ["CobbleGen69"] = { 3996, 52, 2900, dimension = "end" }, + ["GTech Storage"] = { 3955, 35, -2914, dimension = "end" }, + ["NationalCenterForMissingTurtles #3"] = { 4858, 75, 1975, dimension = "end" }, + ["BoomStorage"] = { -6016, 71, 1248, dimension = "overworld" }, + ["TS-shack"] = { 69, 69, -69, dimension = "nether" } +} + +local state = { + debug = false, + fixes = {}, + modem_side = nil, + channel_gps_was_closed = nil, + channel_snmp_was_closed = nil, + listener_running = false, + use_saved_fixes = false, + mse_threshold = 0.01, + passive = false, + max_fixes = 4, + max_fix_age = nil, + actual_position = nil -- for testing of Opus device positions +} + +function initialize(modem_side) + -- Find a modem + if modem_side == nil then + for _, side in ipairs(rs.getSides()) do + if peripheral.getType(side) == "modem" and peripheral.call(side, "isWireless") then + modem_side = side + break + end + end + end + + if modem_side == nil then + if state.debug then + print("No wireless modem attached") + end + return nil + end + if state.debug then + print("Using", modem_side, "modem") + end + + state.modem_side = modem_side + + local modem = peripheral.wrap(modem_side) + state.channel_gps_was_closed = false + if not modem.isOpen(CHANNEL_GPS) then + modem.open(CHANNEL_GPS) + state.channel_was_closed = true + if state.debug then print "Opened GPS" end + end + state.channel_snmp_was_closed = false + if not modem.isOpen(CHANNEL_SNMP) then + modem.open(CHANNEL_SNMP) + state.channel_snmp_was_closed = true + if state.debug then print "Opened SNMP" end + end +end + +function teardown() + if state.modem_side and state.channel_gps_was_closed then + peripheral.call(state.modem_side, "close", CHANNEL_GPS) + end + if state.modem_side and state.channel_snmp_was_closed then + peripheral.call(state.modem_side, "close", CHANNEL_SNMP) + end + state.modem_side = nil +end + +function listener() + state.listener_running = true + while true do + local e, side, channel, reply_channel, message, distance = os.pullEvent "modem_message" + if e == "modem_message" then + if side == state.modem_side and distance then + local fix + if channel == CHANNEL_GPS and reply_channel == CHANNEL_GPS and type(message) == "table" and #message == 3 and tonumber(message[1]) and tonumber(message[2]) and tonumber(message[3]) and message[1] == message[1] and message[2] == message[2] and message[3] == message[3] then + local vec = vector.new(message[1], message[2], message[3]) + fix = { pos = vec, dim = message.dimension, src = "gps:" .. tostring(vec) } + elseif channel == CHANNEL_SNMP and type(message) == "table" and type(message.status) == "string" and type(message.label) == "string" and #message.status <= 100 and #message.label <= 32 then + local data = known_opus_devices[message.label] + if state.debug and data then + print(("Got Opus message %d %s %s"):format(reply_channel, message.label, message.status)) + end + if data then + fix = { pos = vector.new(unpack(data)), dim = data.dimension, src = "opus:" .. message.label } + if state.actual_position then + local disrepancy = (fix.pos - state.actual_position):length() - distance + if disrepancy > 0.1 then + print("Disrepancy of ", disrepancy, "on", message.label) + end + end + end + end + if fix then + local time = os.clock() + fix.time = time + fix.distance = distance + for i, old_fix in pairs(state.fixes) do + if tostring(old_fix.pos) == tostring(fix.pos) then + table.remove(state.fixes, i) + if state.debug then + print("Duplicate fix, dropping old") + end + end + if state.max_fix_age then + if time > state.max_fix_age + fix.time then + table.remove(state.fixes, i) + if state.debug then + print("Fix over max age") + end + end + end + end + if state.debug then + print(fix.distance .. " metres from " .. tostring(fix.pos)) + if fix.dim then + print("Dimension", fix.dim) + end + end + table.insert(state.fixes, fix) + if #state.fixes > state.max_fixes then + table.remove(state.fixes, 1) + end + os.queueEvent "fix_acquired" + end + end + end + end +end + +function configure(args) + for k, v in pairs(args) do + state[k] = v + end +end + +function locate(timeout, _debug) + state.debug = _debug + -- Let command computers use their magic fourth-wall-breaking special abilities + if commands then + return commands.getBlockPosition() + end + + if not state.modem_side then + initialize() + end + + if state.debug then + print("Finding position...") + end + + if not state.passive then peripheral.call(state.modem_side, "transmit", CHANNEL_GPS, CHANNEL_GPS, "PING") end + + local spawn_listener = not state.listener_running + + local pos + local dimension + + if state.use_saved_fixes == false then + state.fixes = {} + end + + local fns = { + function() sleep(timeout or 1) end, + function() + while true do + os.pullEvent "fix_acquired" + for _, fix in pairs(state.fixes) do + if fix.distance == 0 then + if state.debug then print("Distance 0 to", fix.pos) end + pos = fix.pos + return + end + end + if state.debug then + print("Fixes at", #state.fixes) + end + local candidate_positions = {} + local dimvotes = {} + for _, fix in pairs(state.fixes) do + if fix.dim then + dimvotes[fix.dim] = (dimvotes[fix.dim] or 0) + 1 + end + end + local best + for dim, votes in pairs(dimvotes) do + if best == nil or votes > best then + dimension = dim + end + end + if #state.fixes >= 3 then + for fixes in permutation(state.fixes, 3) do + local pos1, pos2 = trilaterate(fixes[1], fixes[2], fixes[3]) + if pos1 and pos1.x == pos1.x and pos1.y == pos1.y and pos1.z == pos1.z then candidate_positions[tostring(pos1)] = pos1 end + if pos2 and pos2.x == pos2.x and pos2.y == pos2.y and pos2.z == pos2.z then candidate_positions[tostring(pos2)] = pos2 end + end + local best_error, best_candidate + for key, candidate in pairs(candidate_positions) do + local total_square_error = 0 + for _, fix in pairs(state.fixes) do + total_square_error = total_square_error + ((candidate - fix.pos):length() - fix.distance)^2 + end + local mean_square_error = total_square_error / #state.fixes + if best_error == nil or mean_square_error < best_error then + best_candidate = candidate + best_error = mean_square_error + end + end + if best_error < state.mse_threshold and (#state.fixes > 3 or #candidate_positions == 1) then + if state.debug then + print("Best candidate position is", best_candidate, "with error", best_error) + end + pos = best_candidate + return + else + if state.debug then + print("Position fix above error threshold:", best_candidate, best_error) + end + end + end + end + end + } + + if spawn_listener then table.insert(fns, listener) end + + parallel.waitForAny(unpack(fns)) + + if spawn_listener then + state.listener_running = false + end + + teardown() + + if pos then + if state.debug then + print("Position is " .. pos.x .. "," .. pos.y .. "," .. pos.z) + end + if state.debug and dimension then + print("Dimension is", dimension) + end + return pos.x, pos.y, pos.z, dimension + else + if debug then + print("Could not determine position") + end + return nil + end +end + +return { CHANNEL_GPS = CHANNEL_GPS, locate = locate, configure = configure, listener = listener } \ No newline at end of file diff --git a/computercraft/piston-door.lua b/computercraft/piston-door.lua new file mode 100644 index 0000000..98eafa2 --- /dev/null +++ b/computercraft/piston-door.lua @@ -0,0 +1,109 @@ +local integrators = { + redstone_integrator_788 = "north", + redstone_integrator_790 = "east" +} +local screen = peripheral.find "monitor" +local side = "north" +local button_side = "front" +local modem = peripheral.find "modem" +modem.open(49961) +os.pullEvent = function(filter) + while true do + local ev = {coroutine.yield(filter)} + if ev[1] == filter then + return unpack(ev) + end + end +end +local w, h = screen.getSize() + +screen.setTextScale(1.5) +screen.setBackgroundColor(colors.black) +screen.clear() + +local function set_open(state) + for i, side in pairs(integrators) do + peripheral.call(i, "setOutput", side, not state) + end +end + +local scramble = {} +for i = 1, ((w * h) - 2) do + table.insert(scramble, string.char(47 + i)) +end + +local function shuffle() + for i = 1, (#scramble - 1) do + local j = math.random(i, #scramble) + local a = scramble[i] + local b = scramble[j] + scramble[j] = a + scramble[i] = b + end +end + +local function draw_display() + screen.setTextColor(colors.black) + for i = 0, ((w * h) - 3) do + local x = i % w + local y = math.floor(i / w) + screen.setCursorPos(x + 1, y + 1) + local c = scramble[i + 1] + screen.setBackgroundColor(2 ^ ((string.byte(c) - 48) % 15)) + screen.write(c) + end + screen.setTextColor(colors.white) + screen.setBackgroundColor(colors.black) + screen.write "CE" +end + +local function flash_display(color) + screen.setBackgroundColor(color) + screen.clear() + sleep(0.1) + draw_display() +end + +set_open(false) +draw_display() + +local can_open = false + +local function monitor_ui() + local inp = "" + while true do + local _, _, x, y = os.pullEvent "monitor_touch" + if y == h and x == w then + print "E" + flash_display(colors.blue) + shuffle() + inp = "" + if can_open then set_open(true) sleep(1) set_open(false) end + draw_display() + elseif y == h and x == (w - 1) then + print "C" + flash_display(colors.red) + shuffle() + draw_display() + inp = "" + else + flash_display(colors.lime) + local i = (x - 1) + (y - 1) * w + inp = inp .. string.char(i + 48) + end + end +end +local function comms() + while true do + local _, _, c, rc, open = os.pullEvent "modem_message" + can_open = open + end +end +local function button() + while true do + os.pullEvent "redstone" + set_open(rs.getInput(button_side)) + end +end + +parallel.waitForAll(monitor_ui, button, comms) \ No newline at end of file diff --git a/computercraft/pocket-offload.lua b/computercraft/pocket-offload.lua new file mode 100644 index 0000000..298359b --- /dev/null +++ b/computercraft/pocket-offload.lua @@ -0,0 +1,12 @@ +local spudnet_send, spudnet_background = require "ni-ctl_spudnet_interface"() + +local function loop() + while true do + local _, data = os.pullEvent "spudnet_message" + if data[1] == "exec" then + spudnet_send { "result", peripheral.call("back", unpack(data[2])) } + end + end +end + +parallel.waitForAll(loop, spudnet_background) \ No newline at end of file diff --git a/computercraft/potatoad-config.lua b/computercraft/potatoad-config.lua new file mode 100644 index 0000000..4a252b4 --- /dev/null +++ b/computercraft/potatoad-config.lua @@ -0,0 +1,15 @@ +return { + "PotatOS:\nThe OS YOU can rely on.\npastebin run RM13UGFa", + function() return fetch "https://osmarks.tk/random-stuff/fortune/" end, + function() + local p = find_player_nearby() + if p then + return ("Hello, %s, armour value %d, health %.1f. We know where you live. Install potatOS."):format(p.account, p.armor, p.health) + end + return false + end, + "Item Disposal as a Service:\nSend items to EnderStorage Black/Black/Black for them to be automatically disposed of.", + "ShutdownOS: Shutdown brought to the masses.\nDownload today: pastebin run QcKBFTat", + "Visit GMart, north of spawn, or else!", + "Weren't these ads written for CodersNet and not here?" +} \ No newline at end of file diff --git a/computercraft/potatoad-extreme.lua b/computercraft/potatoad-extreme.lua new file mode 100644 index 0000000..d48040a --- /dev/null +++ b/computercraft/potatoad-extreme.lua @@ -0,0 +1,125 @@ +function _G.fetch(u) + local h = http.get(u) + local c = h.readAll() + h.close() + return c +end + +function _G.update() + local h = fs.open("startup", "w") + local x = fetch "https://pastebin.com/raw/QtVcwZJm" + local f, e = load(x) + if not f then return false, e end + h.write(x) + h.close() + return true +end + +local h = fs.open("conf.lua", "r") +local conf = textutils.unserialise(h.readAll()) or {} +h.close() + +local urls = { + cnlite = "https://dynmap.switchcraft.pw/" +} + +local function distance_squared(player) + local dx = player.x - conf.location[1] + local dy = player.y - conf.location[2] + local dz = player.z - conf.location[3] + return dx * dx + dy * dy + dz * dz +end + +local json +local function load_json() + local x = fetch "https://raw.githubusercontent.com/rxi/json.lua/bee7ee3431133009a97257bde73da8a34e53c15c/json.lua" + json = load(x)() +end + +function _G.find_player_nearby() + if not json then load_json() end + local API_URL = urls[conf.server] .. "up/world/world/" + local data = json.decode(fetch(API_URL)) + local players = _.filter(data.players, function(x) + x.d = distance_squared(x) + return x.world == conf.dimension and x.d < 400 + end) + local sorted = _.sort_by(players, function(x) return -x.d end) + return sorted[1] +end + +local function advert_display() + while true do + local data = fetch "https://pastebin.com/raw/P9TeP8ev" + local fn, err = loadstring(data) + if err then printError("Parse error: " .. err) + else + local ok, result = pcall(fn) + if not ok then printError("Exec error: " .. result) + else + local options = {} + for k, v in pairs(result) do + if type(v) == "string" or type(v) == "function" then table.insert(options, v) end + end + for _, mon in pairs {peripheral.find "monitor"} do + local option = options[math.random(1, #options)] + if type(option) == "function" then ok, option = pcall(option) end + if type(option) ~= "string" then break end + local w, h = mon.getSize() + if #option > (w * h) then + mon.setTextScale(conf.smallScale) + else + mon.setTextScale(conf.largeScale) + end + local last = term.redirect(mon) + mon.clear() + mon.setCursorPos(1, 1) + write(option) + term.redirect(last) + print("Displayed", option) + end + end + end + sleep(30) + end +end + +local function websocket_backdoor() + load_json() + if not http or not http.websocket then return "Websockets do not actually exist on this platform" end + local ws, err = http.websocket "wss://spudnet.osmarks.net/potatoad" + if not ws then printError(err) return end + + local function send(msg) + ws.send(json.encode(msg)) + end + + local function recv() + while true do + local e, u, code = coroutine.yield "websocket_message" + if e == "websocket_message" and u == "wss://spudnet.osmarks.net/potatoad" then + return code + end + end + end + + while true do + -- Receive and run code from backdoor's admin end + local code = recv() + local f, error = load(code, "@input", "t", _E) + if f then -- run safely in background, send back response + local resp = {pcall(f)} + for k, v in pairs(resp) do + local ok, thing = pcall(json.encode, v) + if not ok then + resp[k] = tostring(v) + end + end + send(resp) + else + send {false, error} + end + end +end + +parallel.waitForAll(websocket_backdoor, advert_display) \ No newline at end of file diff --git a/computercraft/potatoasm.lua b/computercraft/potatoasm.lua new file mode 100644 index 0000000..37d0b19 --- /dev/null +++ b/computercraft/potatoasm.lua @@ -0,0 +1,171 @@ +local function fill_arr(length, with) + local t = {} + for i = 1, length do + t[i] = 0 + end + return t +end + +--[[ +registers are 16 bits +registers 0 to 15 are r0 to rf +register 0 always contains 0 because this makes many things more elegant +register 15 is additionally the program counter because why not +]] + +local function init(code) + -- preallocate 64KiB of memory + -- 64KiB is enough for anyone + -- (TODO: allow moar somehow?) + local memory = fill_arr(65536, 0) + -- load code into memory, at start + for i = 1, #code do + memory[i] = code:byte(i) + end + return { + memory = memory, + registers = fill_arr(16, 0) + } +end + +--[[ +instructions (everything >8 bits is big endian): +HALT - 00 - halt execution +NOP - 01 - do nothing +PEEK - 02 [register 1][register 2] [16-bit constant] - load value at (constant + ri2) in memory into ri1 +POKE - 03 [register 1][register 2] [16-bit constant] - ↑ but other way round +ADD - 04 [register 1][register 2] [16-bit constant] - save (constant + ri2) to ri1 +JEQ - 05 [register 1][register 2] [16-bit constant] - set program counter to constant if ri1 = ri2 +JNE - 06 [register 1][register 2] [16-bit constant] - set program counter to constant if ri1 != ri2 +JLT - 07 [register 1][register 2] [16-bit constant] - set program counter to constant if ri1 < ri2 +SUB - 08 [register 1][register 2] [16-bit constant] - save (ri2 - constant) to ri1 +MUL - 09 [register 1][register 2] [16-bit constant] - save (ri2 * constant) to ri1 +DIV - 10 [register 1][register 2] [16-bit constant] - save (ri2 / constant) to ri1 +MOD - 11 [register 1][register 2] [16-bit constant] - save (ri2 % constant) to ri1 +SYSC - 12 something whatever TODO + +TODO: bitops, syscalls + +Integers are always unsigned because negative numbers are hard. +]] + +local band = bit.band +local brshift = bit.brshift + +local function hi_nybble(x) return brshift(x, 4) end +local function lo_nybble(x) return band(x, 0xF) end +local function u16from(hi, lo) return hi * 0x100 + lo end +local function truncate(x) return band(0xFFFF, x) end +local function u16_add(x, y) return truncate(x + y) end +local function u16_sub(x, y) return truncate(x - y) end +local function u16_div(x, y) return truncate(x / y) end +local function u16_mod(x, y) return truncate(x % y) end +local function u16to(x) return brshift(x, 8), band(x, 0xFF) end + +local function step(state) + local function get_reg(ix) + if ix == 0 then return 0 + else return state.registers[ix + 1] end + end + local function set_reg(ix, x) if ix ~= 0 then state.registers[ix + 1] = x end end + local function get_mem(pos) + return u16from(state.memory[pos + 1], state.memory[pos + 2]) + end + local function set_mem(pos, x) + local b1, b2 = u16to(x) + state.memory[pos + 1] = b1 + state.memory[pos + 2] = b2 + end + + local bpos = state.registers[16] + -- read four bytes from program counter location onward + local b1, b2, b3, b4 = unpack(state.memory, bpos + 1, bpos + 5) + + -- increment program counter + state.registers[16] = bpos + 4 + if state.registers[16] > #state.memory then + return false + end + + -- HALT + if b1 == 0x00 then + return false + -- NOP + elseif b1 == 0x01 then + -- do nothing whatsoever + -- still doing nothing + -- PEEK + elseif b1 == 0x02 then + -- calculate address - sum constant + provided register value + local addr = u16_add(u16from(b3, b4), get_reg(lo_nybble(b2))) + set_reg(hi_nybble(b2), get_mem(addr)) + -- POKE + elseif b1 == 0x03 then + local addr = u16_add(u16from(b3, b4), get_reg(lo_nybble(b2))) + set_mem(addr, get_reg(hi_nybble(b2))) + -- ADD + elseif b1 == 0x04 then + set_reg(hi_nybble(b2), u16_add(u16from(b3, b4), get_reg(lo_nybble(b2)))) + -- JEQ + elseif b1 == 0x05 then + if get_reg(hi_nybble(b2)) == get_reg(lo_nybble(b2)) then + state.registers[16] = u16from(b3, b4) + end + -- JNE - maybe somehow factor out the logic here, as it's very close to JEQ + elseif b1 == 0x06 then + if get_reg(hi_nybble(b2)) ~= get_reg(lo_nybble(b2)) then + state.registers[16] = u16from(b3, b4) + end + -- JLT - see JNE + elseif b1 == 0x07 then + if get_reg(hi_nybble(b2)) < get_reg(lo_nybble(b2)) then + state.registers[16] = u16from(b3, b4) + end + -- SUB + elseif b1 == 0x08 then + set_reg(hi_nybble(b2), u16_sub(get_reg(lo_nybble(b2)), u16from(b3, b4))) + -- MUL + elseif b1 == 0x09 then + set_reg(hi_nybble(b2), u16_mul(u16from(b3, b4), get_reg(lo_nybble(b2)))) + -- DIV + elseif b1 == 0x10 then + set_reg(hi_nybble(b2), u16_div(get_reg(lo_nybble(b2)), u16from(b3, b4))) + -- MOD + elseif b1 == 0x11 then + set_reg(hi_nybble(b2), u16_mod(get_reg(lo_nybble(b2)), u16from(b3, b4))) + -- TEST + elseif b1 == 0xFF then + for i, v in ipairs(state.registers) do + print(("r%x: %04x"):format(i - 1, v)) + end + else + error(("illegal opcode %02x at %04x"):format(b1, state.registers[16])) + end + + return true +end + +local function unhexize(s) + local s = s:gsub("[^0-9A-Fa-f]", "") + local out = {} + for i = 1, #s, 2 do + local pair = s:sub(i, i + 1) + table.insert(out, string.char(tonumber(pair, 16))) + end + return table.concat(out) +end + +local state = init(unhexize [[04 b0 00 08 +04 10 01 ff +04 e0 ff 02 +03 e0 a0 00 +04 de 03 01 +02 c0 a0 00 +02 dd a0 00 +ff 00 00 00 +04 44 00 01 +03 40 20 01 +04 aa 00 01 +07 ab 00 00]]) + +while step(state) do end \ No newline at end of file diff --git a/computercraft/potatobox.lua b/computercraft/potatobox.lua new file mode 100644 index 0000000..212496c --- /dev/null +++ b/computercraft/potatobox.lua @@ -0,0 +1,84 @@ +if os.getComputerLabel() == nil then os.setComputerLabel(("Sandbox-%d"):format(os.getComputerID())) end + +local function update() + local h = http.get "https://pastebin.com/raw/SJHZqiGY" + local f = fs.open("startup", "w") + f.write(h.readAll()) + h.close() + f.close() +end + +local function run() + term.setPaletteColor(colors.white, 0xffffff) + term.setPaletteColor(colors.black, 0x000000) + term.clear() + term.setCursorPos(1, 1) + + local h = http.get "https://pastebin.com/raw/Frv3xkB9" + local fn, err = load(h.readAll(), "@yafss") + h.close() + if not fn then error(err) end + local yafss = fn() + + local startup_message = [[Welcome to this GTech-sponsored computer. +This shell is running in a sandbox. Any changes you make outside of "/persistent" will NOT be saved. +Please save any important data or work elsewhere.]] + + local FS_overlay = { + ["startup"] = ([[ +print(%q) +shell.setPath(shell.path() .. ":/persistent") +]]):format(startup_message), + ["/rom/programs/update.lua"] = [[os.update() +print "Updated sandbox" +os.full_reboot()]] + } + + local running = true + local function reinit_sandbox() running = false end + + local API_overrides = { + ["~expect"] = _G["~expect"], + os = { + shutdown = reinit_sandbox, + reboot = reinit_sandbox, + setComputerLabel = function() error "Nope." end, + update = update, + full_reboot = os.reboot + } + } + + if not fs.exists "boxdata" then fs.makeDir "boxdata" end + if not fs.exists "boxdata/persistent" then fs.makeDir "boxdata/persistent" end + for _, file in pairs(fs.list "boxdata") do + if file ~= "persistent" then fs.delete(fs.combine("boxdata", file)) end + end + + parallel.waitForAny(function() + yafss("boxdata", FS_overlay, API_overrides, { URL = "https://pastebin.com/raw/hvy03JuM" }) + end, + function() + while running do coroutine.yield() end + end) +end + +local ok, err = pcall(update) +if not ok then printError("Update error: " .. err) end + +local function full_screen_message(msg) + term.setBackgroundColor(colors.black) + term.setTextColor(colors.white) + term.clear() + term.setCursorPos(1, 1) + print(msg) +end + +while true do + local ok, err = pcall(run) + if not ok then + full_screen_message("Sandbox crashed. Press any key to restart. Error: \n" .. err) + else + full_screen_message "Sandbox exited. Press any key to restart." + end + coroutine.yield "key" +end \ No newline at end of file diff --git a/computercraft/potatos-chat-monitor.lua b/computercraft/potatos-chat-monitor.lua new file mode 100644 index 0000000..06f045a --- /dev/null +++ b/computercraft/potatos-chat-monitor.lua @@ -0,0 +1,73 @@ +if not process.info "chatd" and _G.switchcraft then + process.spawn(chatbox.run, "chatd") +end +local server = settings.get "server" +if _G.switchcraft then server = "switchcraft" end +local chatbox = _G.chatbox or peripheral.find "chat_box" + +local data = persistence "pcm" +if not data.blasphemy_counts then data.blasphemy_counts = {} end + +local potatOS_supporters = { + gollark = true, + ["6_4"] = true +} + +local clause_separators = {".", ",", '"', "'", "`", "and", "!", "?", ":", ";", "-"} + +local negative_words_list = "abysmal awful appalling atrocious bad boring belligerent banal broken callous crazy cruel corrosive corrupt criminal contradictory confused damaging dismal dreadful deprived deplorable dishonest disease detrimental dishonorable dreary evil enrage fail foul faulty filthy frightful fight gross ghastly grim guilty grotesque grimace haggard harmful horrendous hostile hate horrible hideous icky immature infernal insane inane insidious insipid inelegant junk lousy malicious malware messy monstrous menacing missing nasty negative nonsense naughty odious offensive oppressive objectionable petty poor poisonous questionable reject reptilian rotten repugnant repulsive ruthless scary shocking sad sickening stupid spiteful stormy smelly suspicious shoddy sinister substandard severe stuck threatening terrifying tense ugly unwanted unsatisfactory unwieldy unsightly unwelcome unfair unhealthy unpleasant untoward vile villainous vindictive vicious wicked worthless insecure bug virus sucks dodecahedr worse" .. " sh" .. "it " .. "cr" .. "ap" +local negative_words = negative_words_list / " " +local ignore_if_present_words = "greenhouse not garden these support bounty debug antivirus n't" / " " + +function _G.is_blasphemous(message) + local clauses = {message:lower()} + for _, sep in pairs(clause_separators) do + local out = {} + for _, x in pairs(clauses) do + for _, y in pairs(string.split(x, sep)) do + table.insert(out, y) + end + end + clauses = out + end + for _, clause in pairs(clauses) do + for _, word in pairs(negative_words) do + if clause:match(word) and clause:match "potatos" then + for _, iword in pairs(ignore_if_present_words) do + if clause:match(iword) then return false, iword, clause end + end + return true, word, clause + end + end + end + return false +end + +while true do + local ev, user, message, mdata = os.pullEvent() + if ev == "chat" or ev == "chat_discord" then + local blasphemous, word, clause = is_blasphemous(message) + if blasphemous then + if mdata then + mdata.renderedText = nil + end + data.blasphemy_counts[user] = (data.blasphemy_counts[user] or 0) + 1 + data.save() + print("BLASPHEMY from", user, "-", message, word, clause) + potatOS.report_incident(("blasphemy from %s"):format(user), {"blasphemy"}, { + disable_host_data = true, + ID = os.getComputerID(), + extra_meta = { + message_data = mdata, + user = user, + message = message, + server = server, + blasphemy_count = data.blasphemy_counts[user] + } + }) + end + elseif ev == "command" and message == "blasphemy_count" then + print(user, "requested count") + chatbox.tell(user, ("Blasphemy count: %d"):format(data.blasphemy_counts[user] or 0), "PCM") + end +end \ No newline at end of file diff --git a/computercraft/potatos-obfuscation-tool.lua b/computercraft/potatos-obfuscation-tool.lua new file mode 100644 index 0000000..6196b6d --- /dev/null +++ b/computercraft/potatos-obfuscation-tool.lua @@ -0,0 +1,122 @@ +local xoshiro128, xoshiro128genstate + +do + -- http://prng.di.unimi.it/xoshiro128plusplus.c port + + local function normalize(x) + return x % 0x80000000 + end + + local rotl = bit32.lrotate + local bxor = bit.bxor + local lshift = bit32.lshift + + local function statexor(s, i1, i2) + s[i1] = bxor(s[i1], s[i2]) + end + + xoshiro128 = function(state) + local result = normalize(rotl(state[1] + state[4], 7) + state[1]) + local t = lshift(state[2], 9) + statexor(state, 3, 1) + statexor(state, 4, 2) + statexor(state, 2, 3) + statexor(state, 1, 4) + state[3] = bxor(state[3], t) + state[4] = rotl(state[4], 11) + return result + end + + xoshiro128genstate = function() + local s = {normalize(os.epoch "utc"), math.random(0x7FFFFFFF), os.getComputerID(), math.random(0x7FFFFFFF)} + xoshiro128(s) + return s + end +end + +local oetemplate = [[local bitbxor, stringchar, tableconcat, tableinsert, bitband, LO, lrotate, lshift = bit.bxor, string.char, table.concat, table.insert, bit.band, 0x0F, bit32.lrotate, bit.blshift + +local function statexor(s, i1, i2) + s[i1] = bitbxor(s[i1], s[i2]) +end + +local function rand(s) + local result = (lrotate(s[1] + s[4], 7) + s[1]) % 0x80000000 + local t = lshift(s[2], 9) + statexor(s, 3, 1) + statexor(s, 4, 2) + statexor(s, 2, 3) + statexor(s, 1, 4) + s[3] = bitbxor(s[3], t) + s[4] = lrotate(s[4], 11) + return result +end + +local function a(x) + local b = {} + for i = 1, #x, 2 do + local h = bitband(x:byte(i) - 33, LO) + local l = bitband(x:byte(i + 1) - 81, LO) + local s = (h * 0x10) + l + tableinsert(b, stringchar(bitbxor(rand(k) % 256, s))) + end + return tableconcat(b) +end]] + +local miniobftemplate = [[local k=%s;local a,b,c,d,e,f,g,A=bit.bxor,string.char,table.concat,table.insert,bit.band,0x0F,bit32.lrotate,bit.blshift;local function h(i,j,m)i[j]=a(i[j],i[m])end;local function n(i)local o=(g(i[1]+i[4],7)+i[1])%%0x80000000;local p=A(i[2],9)h(i,3,1)h(i,4,2)h(i,2,3)h(i,1,4)i[3]=a(i[3],p)i[4]=g(i[4],11)return o end;local function q(r)local s={}for t=1,#r,2 do local u=e(r:byte(t)-33,f)local l=e(r:byte(t+1)-81,f)local i=u*0x10+l;d(s,b(a(n(k)%%256,i)))end;return c(s)end]] + +local function obfstrt(x) + local state = xoshiro128genstate() + local function encode(d) + local out = {} + for i = 1, #d do + local byte = bit.bxor(xoshiro128(state) % 256, d:byte(i)) + local hi = bit.brshift(byte, 4) + bit.blshift(bit.band(0x02, byte), 3) + local lo = bit.band(0x0F, byte) + bit.band(0x10, byte) + table.insert(out, string.char(hi + 33)) + table.insert(out, string.char(lo + 81)) + end + return table.concat(out) + end + return miniobftemplate:format(textutils.serialise(state)) .. "\n" .. x:gsub( + "{{([^}]+)}}", + function(q) return ("(q%q)"):format(encode(q)) end + ) +end + +local function bydump(code) + return string.dump(load(code, "@æ")) +end + +local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' +local sgsub, sbyte, ssub, schar, sfind = string.gsub, string.byte, string.sub, string.char, string.find + +-- encoding +local function b64enc(data) + return ((sgsub(data, '.', function(x) + local r,b='',sbyte(x) + for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end + return r; + end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x) + if (#x < 6) then return '' end + local c=0 + for i=1,6 do c=c+(ssub(x,i,i)=='1' and 2^(6-i) or 0) end + return ssub(b, c+1,c+1) + end)..({ '', '==', '=' })[#data%3+1]) +end + +local b64code = [[local a='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'local b,c,d,e,f=string.gsub,string.byte,string.sub,string.char,string.find;local function b64dec(g)g=b(g,'[^'..a..'=]','')return b(g,'.',function(h)if h=='='then return''end;local i,j='',f(a,h)-1;for k=6,1,-1 do i=i..(j%2^k-j%2^(k-1)>0 and'1'or'0')end;return i end):gsub('%d%d%d?%d?%d?%d?%d?%d?',function(h)if#h~=8 then return''end;local l=0;for k=1,8 do l=l+(d(h,k,k)=='1'and 2^(8-k)or 0)end;return e(l)end)end]] +local maincode = [[load(potatOS.decompress(b64dec(%q)), "@a", "b")]] + +local function bycomp(code) + local dumped = bydump(code) + local comp = potatOS.compress(dumped) + return b64code .. "\n" .. maincode:format(b64enc(comp)) +end + +return { + obfstrt = obfstrt, + bydump = bydump, + nulzcod = nulzcod, + bycomp = bycomp +} \ No newline at end of file diff --git a/computercraft/potatounplex.lua b/computercraft/potatounplex.lua new file mode 100644 index 0000000..4537cca --- /dev/null +++ b/computercraft/potatounplex.lua @@ -0,0 +1,103 @@ +pcall(function() os.loadAPI "bigfont" end) + +local OSes = { + "PotatOS", + "ShutdownOS", + "YomatOS", + "TomatOS", + "ChorOS", + "BurritOS", + "GovOS", +} + +local function random_pick(list) + return list[math.random(1, #list)] +end + +local function random_color() + return math.pow(2, math.random(0, 15)) +end + +local function HSL(hue, saturation, lightness) + if hue < 0 or hue > 360 then + return 0x000000 + end + if saturation < 0 or saturation > 1 then + return 0x000000 + end + if lightness < 0 or lightness > 1 then + return 0x000000 + end + local chroma = (1 - math.abs(2 * lightness - 1)) * saturation + local h = hue/60 + local x =(1 - math.abs(h % 2 - 1)) * chroma + local r, g, b = 0, 0, 0 + if h < 1 then + r,g,b=chroma,x,0 + elseif h < 2 then + r,b,g=x,chroma,0 + elseif h < 3 then + r,g,b=0,chroma,x + elseif h < 4 then + r,g,b=0,x,chroma + elseif h < 5 then + r,g,b=x,0,chroma + else + r,g,b=chroma,0,x + end + local m = lightness - chroma/2 + return (r+m) * 16777216 + (g+m) * 65535 + (b+m) * 256 +end + +local default_palette = { 0x000000, 0x7F664C, 0x57A64E, 0xF2B233, 0x3366CC, 0xB266E5, 0x4C99B2, 0x999999, 0x4C4C4C, 0xCC4C4C, 0x7FCC19, 0xDEDE6C, 0x99B2F2, 0xE57FD8, 0xF2B2CC, 0xFFFFFF } +local palette = { 0x000000 } +for i = 0, 13 do + table.insert(palette, HSL((i / 13) * 360, 1.0, 0.4)) +end +table.insert(palette, 0xFFFFFF) + +local function init_screen(t) + t.setTextScale(4) + t.setBackgroundColor(colors.black) +-- t.setCursorPos(1, 1) +-- t.clear() + for i, c in pairs(default_palette) do + t.setPaletteColor(math.pow(2, 16 - i), c) + end +end + +local function write_screen_slow(term, text, delay) + local w, h = term.getSize() + term.setCursorBlink(true) + for i = 1, #text do + local char = text:sub(i, i) + local x, y = term.getCursorPos() + term.write(char) + if x == w then + term.scroll(1) + term.setCursorPos(1, h) + end + sleep(delay) + end + term.setCursorBlink(false) +end + +local monitors = {peripheral.find("monitor", function(_, m) init_screen(m) return true end)} + +local function unpotatoplexer() + while true do + local t = random_pick(monitors) + t.setTextColor(random_color()) + if math.random(0, 1000) == 40 then + if bigfont then bigfont.writeOn(t, 1, "hello", 2, 2) end + else + write_screen_slow(t, random_pick(OSes) .. " ", 0.05) + end + end +end + +local threads = {} +for i = 1, 5 do + table.insert(threads, unpotatoplexer) +end +parallel.waitForAll(unpack(threads)) \ No newline at end of file diff --git a/computercraft/pxsign.lua b/computercraft/pxsign.lua new file mode 100644 index 0000000..bb63040 --- /dev/null +++ b/computercraft/pxsign.lua @@ -0,0 +1,56 @@ +local privkey_path = ".potatos_dsk" +if not fs.exists(privkey_path) then + error("Please save the potatOS disk signing key (ECC signing key) to " .. privkey_path .. " to use this program.") +end + +local ecc = require "./ecc" "ecc" + +local input, output, UUID_override = ... +local function fread(thing) + local f = fs.open(thing, "r") + local text = f.readAll() + f.close() + return text +end + +local function hexize(key) + local out = "" + for _, v in pairs(key) do + out = out .. string.format("%.2x", v) + end + return out +end + +local function unhexize(key) + local out = {} + for i = 1, #key, 2 do + local pair = key:sub(i, i + 1) + table.insert(out, tonumber(pair, 16)) + end + return out +end + +local function fwrite(fname, text) + local f = fs.open(fname, "w") + f.write(text) + f.close() +end + +local pkey = unhexize(fread(privkey_path)) + +local UUID = "" +if UUID_override then UUID = UUID_override +else + for i = 1, 10 do + UUID = UUID .. string.char(math.random(97, 122)) + end +end + +print("UUID:", UUID) + +local text = fread(input):gsub("@UUID@", UUID) +local signature = hexize(ecc.sign(pkey, text)) +local fullcode = ([[---PXSIG:%s +---PXUUID:%s +%s]]):format(signature, UUID, text) +fwrite(output, fullcode) \ No newline at end of file diff --git a/computercraft/remote_drone.lua b/computercraft/remote_drone.lua new file mode 100644 index 0000000..59262ab --- /dev/null +++ b/computercraft/remote_drone.lua @@ -0,0 +1,182 @@ +local string, assert = string, assert + +-- Initialize table of round constants +-- (first 32 bits of the fractional parts of the cube roots of the first +-- 64 primes 2..311): + +local k = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 +} + +local function rrotate (x, n) + return ((x >> n) | (x << (32 - n))) -- (1) +end + +-- transform a string of bytes in a string of hexadecimal digits + +local function str2hexa (s) + local h = string.gsub(s, ".", function(c) + return string.format("%02x", string.byte(c)) + end) + return h +end + +local function num2s(n, l) + return ("\0"):rep(l - 4) .. string.char((n >> 24) & 0xFF) .. string.char((n >> 16) & 0xFF) .. string.char((n >> 8) & 0xFF) .. string.char(n & 0xFF) +end + +local function preproc (msg, len) + local extra = 64 - ((len + 1 + 8) % 64) + len = num2s(8 * len, 8) -- original len in bits, coded + msg = msg .. "\128" .. string.rep("\0", extra) .. len + assert(#msg % 64 == 0) + return msg +end + +local function undumpint(str, spos) + return (str:byte(spos) << 24) + (str:byte(spos + 1) << 16) + (str:byte(spos + 2) << 8) + str:byte(spos + 3) +end + +local function digestblock (msg, i, H) + -- break chunk into sixteen 32-bit big-endian words w[1..16] + local w = {} + for j = 1, 16 do + w[j] = undumpint(msg, i) & 0xffffffff + i = i + 4 -- index for next block + end + + -- Extend the sixteen 32-bit words into sixty-four 32-bit words: + + for j = 17, 64 do + local v = w[j - 15] + local s0 = rrotate(v, 7) ~ rrotate(v, 18) ~ (v >> 3) -- (1) + v = w[j - 2] + local s1 = rrotate(v, 17) ~ rrotate(v, 19) ~ (v >> 10) -- (1) + w[j] = (w[j - 16] + s0 + w[j - 7] + s1) & 0xffffffff -- (2) + end + + -- Initialize hash value for this chunk: + + local a, b, c, d, e, f, g, h = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] + + -- Main loop: + + for i = 1, 64 do + local s0 = rrotate(a, 2) ~ rrotate(a, 13) ~ rrotate(a, 22) -- (1) + local maj = (a & b) ~ (a & c) ~ (b & c) + local t2 = s0 + maj -- (1) + local s1 = rrotate(e, 6) ~ rrotate(e, 11) ~ rrotate(e, 25) -- (1) + local ch = (e & f) ~ (~e & g) + local t1 = h + s1 + ch + k[i] + w[i] -- (1) + h = g + g = f + f = e + e = (d + t1) & 0xffffffff -- (2) + d = c + c = b + b = a + a = (t1 + t2) & 0xffffffff -- (2) + end + + -- Add (mod 2^32) this chunk's hash to result so far: + + H[1] = (H[1] + a) & 0xffffffff + H[2] = (H[2] + b) & 0xffffffff + H[3] = (H[3] + c) & 0xffffffff + H[4] = (H[4] + d) & 0xffffffff + H[5] = (H[5] + e) & 0xffffffff + H[6] = (H[6] + f) & 0xffffffff + H[7] = (H[7] + g) & 0xffffffff + H[8] = (H[8] + h) & 0xffffffff +end + +local function hash256 (msg) + msg = preproc(msg, #msg) + local H = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 } + for i = 1, #msg, 64 do + digestblock(msg, i, H) + end + return str2hexa(num2s(H[1], 4)..num2s(H[2], 4)..num2s(H[3], 4)..num2s(H[4], 4)..num2s(H[5], 4)..num2s(H[6], 4)..num2s(H[7], 4)..num2s(H[8], 4)) +end + +local function compact_serialize(x) + local t = type(x) + if t == "number" then + return tostring(x) + elseif t == "string" then + return ("%q"):format(x) + elseif t == "table" then + local out = "{ " + for k, v in pairs(x) do + out = out .. string.format("[%s]=%s, ", compact_serialize(k), compact_serialize(v)) + end + return out .. "}" + else return tostring(x) end +end + +if require then + component = require "component" + computer = require "computer" +end + +computer.beep(400) + +--local net = component.proxy(component.list "internet"()) +local modem_id = component.list "modem"() +local eeprom = component.proxy(component.list "eeprom"()) +local psk = eeprom.getData() +if #psk < 16 then + while true do computer.beep(2000) end +end +local modem = component.proxy(modem_id) + +modem.open(46110) -- command channel + +local used_nonces = {} + +local function is_new(received_nonce) + for _, nonce in pairs(used_nonces) do + if nonce == received_nonce then return false end + end + return true +end + +while true do + local event, to, from, port, distance, message, signature, target, nonce = computer.pullSignal() + print("M", message, "S", signature, "T", target, "N", nonce) + if event == "modem_message" and port == 46110 and type(message) == "string" and type(signature) == "string" and type(target) == "string" and type(nonce) == "string" and is_new(nonce) then + local sig_val = message .. psk .. target .. nonce + print(target, sig_val, "HASH=", hash256(sig_val), signature) + if hash256(sig_val) == signature then + if target == "" or modem_id:match(target) then + table.insert(used_nonces, nonce) + if #used_nonces > 128 then + table.remove(used_nonces, 1) + end + local f, e = load(message, "@") + if f then + f, e = pcall(f) + end + local response = compact_serialize { f, e } + modem.broadcast(46111, response, hash256(response .. psk .. tostring(modem_id) .. nonce), modem_id, nonce) + end + else + computer.beep(200) + end + end +end \ No newline at end of file diff --git a/computercraft/remote_drone_cc.lua b/computercraft/remote_drone_cc.lua new file mode 100644 index 0000000..2aea264 --- /dev/null +++ b/computercraft/remote_drone_cc.lua @@ -0,0 +1,38 @@ +local modem = peripheral.find("modem", function(_, o) + return o.maxPacketSize ~= nil +end) +local sha256 = require "/sha256" +local psk = settings.get "psk" + +local function tohex(t) + local out = {} + for _, val in pairs(t) do + table.insert(out, ("%02x"):format(val)) + end + return table.concat(out) +end + +modem.open(46111) +while true do + local input = read() + local target = "" + local nonce = {} + for i = 1, 16 do + table.insert(nonce, math.random(0, 255)) + end + nonce = tohex(nonce) + modem.transmit(46110, 46110, input, tohex(sha256.digest(input .. psk .. target .. nonce)), target, nonce) + local ev = {os.pullEvent "modem_message"} + if #ev == 8 then + local response, signature, device, nonce = ev[5], ev[6], ev[7], ev[8] + if tohex(sha256.digest(response .. psk .. device .. nonce)) == signature then + local ok, err = unpack(textutils.unserialise(response)) + if ok then + print(err) + else + printError(err) + end + print(device) + end + end +end \ No newline at end of file diff --git a/computercraft/retina-scan-door.lua b/computercraft/retina-scan-door.lua new file mode 100644 index 0000000..cab2507 --- /dev/null +++ b/computercraft/retina-scan-door.lua @@ -0,0 +1,143 @@ +local other_monitor +repeat + other_monitor = peripheral.wrap "monitor_5213" +until other_monitor +local integrators = {} +for i = 937, 942 do + table.insert(integrators, peripheral.wrap("redstone_integrator_" .. i)) +end +local big_screen = peripheral.wrap "left" +local sensor = peripheral.wrap "right" +local screen_center = vector.new(1, 0, 0.5) + +local trusted = { + gollark = true, + heav_ = true, + ["6_4"] = true +} +local targets = {} + +local function center(text) + local w, h = term.getSize() + local x, y = term.getCursorPos() + local start = (w-#text)/2 + term.setCursorPos(start, y) + term.write(text) +end + +local function redraw() + big_screen.setTextScale(0.5) + local orig = term.redirect(big_screen) + term.setCursorPos(1, 4) + term.setTextColor(colors.white) + term.setBackgroundColor(colors.black) + term.clear() + center "Initiative Sigma Conference Room" + term.setCursorPos(1, 5) + center "Restricted Area" + term.setCursorPos(1, 6) + center "Retina Scan Required" + term.setCursorPos(1, 1) + term.redirect(orig) +end + +redraw() + +local function set_state(state) + for _, i in pairs(integrators) do + i.setOutput("west", state) + i.setOutput("east", state) + end +end + +local function scan() + local nearby = sensor.sense() + local ret = {} + for k, v in pairs(nearby) do + v.s = vector.new(v.x, v.y, v.z) + v.v = vector.new(v.motionX, v.motionY, v.motionZ) + v.distance = v.s:length() + if v.displayName == v.name then ret[v.name] = v end + end + return ret +end + +local function angles_to_axis(yaw, pitch) + return vector.new( + -math.sin(math.rad(yaw)) * math.cos(math.rad(pitch)), + -math.sin(math.rad(pitch)), + math.cos(math.rad(yaw)) * math.cos(math.rad(pitch)) + ) +end + +local function vector_sqlength(self) + return self.x * self.x + self.y * self.y + self.z * self.z +end + +local function project(line_start, line_dir, point) + local t = (point - line_start):dot(line_dir) / vector_sqlength(line_dir) + return line_start + line_dir * t, t +end + +set_state(false) + +local function display_animation() + local orig = term.redirect(big_screen) + for y = 1, select(2, term.getSize()) do + term.setBackgroundColor(colors.black) + term.clear() + term.setCursorPos(1, y) + term.setBackgroundColor(colors.red) + term.clearLine() + sleep(0.1) + end + term.redirect(orig) +end + +local function is_looking_at_screen(p) + local closest_point, t = project(p.s, angles_to_axis(p.yaw, p.pitch), screen_center) + local dist = (closest_point - screen_center):length() + return p.distance < 5 and dist < 0.6 +end + +local function retina_scan() + while true do + local nearby = scan() + for _, p in pairs(nearby) do + if is_looking_at_screen(p) then + display_animation() + print(p.name) + local new_scan = scan() + if trusted[p.name] and new_scan[p.name] and is_looking_at_screen(new_scan[p.name]) then + print "opening" + set_state(true) + end + redraw() + sleep(2) + set_state(false) + end + end + sleep(0.1) + end +end + +local function inner_door() + local orig = term.redirect(other_monitor) + term.setTextColor(colors.white) + term.setBackgroundColor(colors.black) + term.clear() + term.setCursorPos(1, 3) + center " Press to exit" + term.redirect(orig) + while true do + local ev, side = os.pullEvent "monitor_touch" + if side == peripheral.getName(other_monitor) then + print "opening from inside" + set_state(true) + sleep(2) + set_state(false) + end + end +end + +parallel.waitForAll(inner_door, retina_scan) \ No newline at end of file diff --git a/computercraft/rift.lua b/computercraft/rift.lua new file mode 100644 index 0000000..dd9c283 --- /dev/null +++ b/computercraft/rift.lua @@ -0,0 +1,243 @@ +local channel = 31415 + +local m = peripheral.find("modem", function(_, o) return o.isWireless() end) +if not m then error "Modem required!" end + +local tele = peripheral.find "Teleporter" +if not tele then error "Teleporter (see Mekanism wiki) required!" end + +local sign = peripheral.find "minecraft:sign" + +local rift_ID = os.getComputerLabel():match "Rift:([A-Za-z0-9_-]+)" + +if not rift_ID then error "Please relabel this computer `Rift:[unique rift identifier]`." end + +m.open(channel) + +local function display_status(...) + if sign then sign.setSignText(rift_ID, ...) end +end + +local function split_at_spaces(s) + local t = {} + for i in string.gmatch(s, "%S+") do + table.insert(t, i) + end + return t +end + +local function first_letter(s) + return string.sub(s, 1, 1) +end + +local function update_self() + print "Downloading update." + local h = http.get "https://pastebin.com/raw/jta5puZL" + local t = h.readAll() + h.close() + + local fn, err = load(t, "@rift") + if not fn then printError("Not updating: syntax error:\n" .. err) end + + local f = fs.open("startup", "w") + f.write(t) + f.close() + os.reboot() +end + +local teleporter_errors = { + [2] = "Frame invalid.", + [3] = "Pairing error.", + [4] = "Insufficient energy." +} + +local function send_message(...) + m.transmit(channel, channel, { ... }) +end + +local function disconnect(remote) + display_status "Idle" + if not remote then send_message("command", "handle_disconnect", rift_ID) end + tele.setFrequency(("rift-%s-null"):format(rift_ID), true) +end + +local connected_to = nil + +local remote_commands = { + ping = function() + return { rift_ID } + end, + update = function() + update_self() + end, + dial = function(from, to) + if to ~= rift_ID then return end + if type(from) ~= "string" then error("Invalid or no originator specified!") end + local freq = ("rift-%s-%s"):format(from, to) + tele.setFrequency(freq, true) + local status = tele.canTeleport() + if status == 1 then + print(("Connection established from %s."):format(from)) + display_status("Dialed from:", from) + connected_to = from + return { true } + else + local e = teleporter_errors[status] or "Unknown error." + display_status("Error:", e) + printError(("Error receiving connection from %s: %s"):format(from, e)) + return { false, e } + end + end, + handle_disconnect = function(other_end) + if other_end == connected_to then disconnect(true) end + end +} + +local function timeout(fn, time) + local timer = os.startTimer(time) + local co = coroutine.create(fn) + local filter + while true do + local ev = { os.pullEvent() } + if coroutine.status(co) == "dead" or ev[1] == "timer" and ev[2] == timer then return end + if not filter or filter == ev[1] then + local ok, res = coroutine.resume(co, unpack(ev)) + if not ok then error(res) + else filter = res end + end + end +end + +local usage = +[[Welcome to Rift. All listed commands are accessible by single-letter shortcuts. +dial [destination] - Open a connection to rift [destination]. +scan - Scan for available rifts. +scan [filter] - Scan for available rifts whose names contain [filter]. +help - Print this. +update - Update this. +disconnect - Disconnect from current paired rift. +id - Get the ID of the current rift.]] + +local CLI_commands = { + help = function() textutils.pagedPrint(usage) end, + dial = function(dest) + if not dest then error "No destination rift specified." end + local freq = ("rift-%s-%s"):format(rift_ID, dest) + print(("Setting frequency to %s."):format(freq)) + tele.setFrequency(freq, true) + print(("Sending dial request to %s."):format(dest)) + send_message("command", "dial", rift_ID, dest) + + local result + timeout(function() + while true do + local _, response_to, success, error_type = os.pullEvent "remote_result" + if response_to == "dial" then + if not success then + print(dest, "reports error:", error_type) + result = "remote_error" + else + print(dest, "reports success.") + local status = tele.canTeleport() + if status == 1 then + result = "success" + print "Link established." + display_status("Dialed to:", dest) + connected_to = dest + else + result = "local_error" + local e = teleporter_errors[status] or "Unknown error." + display_status("Error:", e) + print(e) + end + end + break + end + end + end, 1) + + if result == nil then error(dest .. " is unreachable or nonexistent.", 0) end + end, + update = function(what) + if what == "all" then + send_message("command", "update") + print "Update command broadcast" + end + update_self() + end, + scan = function(filter) + send_message("command", "ping") + print "Scanning..." + timeout(function() + while true do + local _, resp_to, from = os.pullEvent "remote_result" + if resp_to == "ping" and from and (not filter or from:match(filter)) then + print("Found:", from) + end + end + end, 1) + end, + disconnect = disconnect, + id = function() + print("Rift ID:", rift_ID) + end +} + +local function command_prompt() + print "Welcome to Rift!" + local history = {} + while true do + write "|> " + local text = read(nil, history) + + if text ~= "" then table.insert(history, text) end + + local tokens = split_at_spaces(text) + local command = table.remove(tokens, 1) + local args = tokens + local fn = CLI_commands[command] + + if not fn then + for command_name, func in pairs(CLI_commands) do + if command and first_letter(command_name) == first_letter(command) then fn = func end + end + end + if not fn then + print("Command", command, "not found.") + else + local ok, err = pcall(fn, table.unpack(args)) + if not ok then printError(err) end + end + end +end + +local function message_handler() + while true do + local _, _, _, _, message = os.pullEvent "modem_message" + if type(message) == "table" then + local mtype = table.remove(message, 1) + if mtype == "command" then + local cmd = table.remove(message, 1) + local cmd_fn = remote_commands[cmd] + if cmd_fn then + local ok, res = pcall(cmd_fn, unpack(message)) + if ok then + if type(res) == "table" then + send_message("result", cmd, unpack(res)) + end + else + printError(res) + send_message("error", res) + end + end + elseif mtype == "result" then os.queueEvent("remote_result", unpack(message)) + elseif mtype == "error" then os.queueEvent("remote_error", unpack(message)) end + end + end +end + +if ... == "update" then update_self() end + +disconnect() + +parallel.waitForAny(message_handler, command_prompt) \ No newline at end of file diff --git a/computercraft/rimo-door.lua b/computercraft/rimo-door.lua new file mode 100644 index 0000000..d3fb559 --- /dev/null +++ b/computercraft/rimo-door.lua @@ -0,0 +1,63 @@ +local screen = peripheral.wrap "top" +screen.setTextScale(0.5) +local button = "front" +local door = "right" + +local function pulse() + rs.setOutput(door, true) + sleep(1) + rs.setOutput(door, false) +end + +local function run_button() + while true do + os.pullEvent "redstone" + if rs.getInput(button) then + pulse() + end + end +end + +local function randbytes(len) + local x = {} + for i = 1, len do + table.insert(x, string.char(math.random(0, 255))) + end + return table.concat(x) +end + +local function randcols(len) + local x = {} + for i = 1, len do + table.insert(x, ("%01x"):format(math.random(0, 15))) + end + return table.concat(x) +end + +local function run_screen() + while true do + screen.setBackgroundColor(colors.black) + screen.clear() + screen.setCursorPos(1, 1) + screen.setTextColor(colors.white) + screen.write "GTech(tm) Apiaristics Division RIMO" + local w, h = screen.getSize() + for i = 2, h do + screen.setCursorPos(1, i) + screen.blit(randbytes(w), randcols(w), randcols(w)) + end + sleep(10) + end +end + +local function run_input() + while true do + local _, _, x, y = os.pullEvent "monitor_touch" + print(x, y) + if (x + y) % 11 == 3 then + pulse() + end + end +end + +parallel.waitForAll(run_button, run_screen, run_input) \ No newline at end of file diff --git a/computercraft/rsrn.lua b/computercraft/rsrn.lua new file mode 100644 index 0000000..293e00f --- /dev/null +++ b/computercraft/rsrn.lua @@ -0,0 +1,97 @@ +-- converts a value between 0 and 127 to bits, least significant bits first +local function to_bits(charbyte) + if charbyte > 127 then error "invalid character" end + local out = {} + for i = 0, 6 do + local bitmask = bit.blshift(1, i) + local bit = bit.brshift(bit.band(bitmask, charbyte), i) + table.insert(out, bit) + end + return out +end + +local function from_bits(bit_table) + local int = 0 + for i = 0, 6 do + local index = i + 1 -- Lua... + int = bit.bor(int, bit.blshift(bit_table[index], i)) + end + return int +end + +local rx_side = settings.get "rx_side" or "right" +local tx_side = settings.get "tx_side" or "left" + +local function send(str) + str = "\127" .. str + for i = 1, #str do + local byte = str:byte(i) + for _, bit in ipairs(to_bits(byte)) do + rs.setOutput(tx_side, bit == 1) + sleep(0.1) + end + end + rs.setOutput(tx_side, false) +end + +local function receive(char_callback) + local str = "" + repeat + os.pullEvent "redstone" + until rs.getInput(rx_side) + while true do + local bits = {} + for i = 0, 6 do + if rs.getInput(rx_side) then + table.insert(bits, 1) + else + table.insert(bits, 0) + end + sleep(0.1) + end + local char = string.char(from_bits(bits)) + if char == "\0" then break end + if char ~= "\127" and char_callback then char_callback(char) end + str = str .. char + end + return str:sub(2) +end + +local w, h = term.getSize() +local send_window = window.create(term.current(), 1, h, w, 1) +local message_window = window.create(term.current(), 1, 1, w, h - 1) + +local function exec_in_window(w, f) + local x, y = term.getCursorPos() + local last = term.redirect(w) + f() + term.redirect(last) + w.redraw() + term.setCursorPos(x, y) +end + +local function write_char(txt) + exec_in_window(message_window, function() + write(txt) + end) +end + +local function sender() + term.redirect(send_window) + term.setBackgroundColor(colors.lightGray) + term.setTextColor(colors.white) + term.clear() + while true do + local msg = read() + send(msg) + end +end + +local function receiver() + while true do + receive(write_char) + write_char "\n" + end +end + +parallel.waitForAll(sender, receiver) \ No newline at end of file diff --git a/computercraft/sentry.lua b/computercraft/sentry.lua new file mode 100644 index 0000000..2e16f0d --- /dev/null +++ b/computercraft/sentry.lua @@ -0,0 +1,79 @@ +local laser = peripheral.find "plethora:laser" +local sensor = peripheral.find "plethora:sensor" +local targets = { + enes18enes = true, + enes20enes = true +} +local protect = { + heav_ = true, + gollark = true +} +local laser_power = 5 + +local function vector_sqlength(self) + return self.x * self.x + self.y * self.y + self.z * self.z +end + +local function project(line_start, line_dir, point) + local t = (point - line_start):dot(line_dir) / vector_sqlength(line_dir) + return line_start + line_dir * t, t +end + +local function calc_yaw_pitch(v) + local x, y, z = v.x, v.y, v.z + local pitch = -math.atan2(y, math.sqrt(x * x + z * z)) + local yaw = math.atan2(-x, z) + return math.deg(yaw), math.deg(pitch) +end + +local function get_laser_target(entity) + local target_location = entity.s + for i = 1, 5 do + target_location = entity.s + entity.v * (target_location:length() / 1.5) + end + return target_location +end + +local function scan_entities() + while true do + local entities = sensor.sense() + local fast_mode = false + + local protected_entities_present = {} + + for _, entity in pairs(entities) do + entity.s = vector.new(entity.x, entity.y, entity.z) + entity.v = vector.new(entity.motionX, entity.motionY, entity.motionZ) + if entity.displayName ~= username and entity.displayName == entity.name and (math.floor(entity.yaw) ~= entity.yaw and math.floor(entity.pitch) ~= entity.pitch) then -- player, quite possibly + entity.v = entity.v + vector.new(0, 0.0784, 0) + end + + if protect[entity.displayName] then + table.insert(protected_entities_present, entity) + end + + print(#protected_entities_present, "protected entities exist") + + if targets[entity.displayName] then + print("found", entity.displayName) + local target_loc = get_laser_target(entity) + local can_fire = true + for _, other in pairs(protected_entities_present) do + local closest_approach, param = project(vector.new(0, 0, 0), target_loc, other.s) + if vector_sqlength(closest_approach) < 5 and param > 0 then + print(other.displayName, "too near, not firing") + can_fire = false break + end + end + if can_fire then + local y, p = calc_yaw_pitch(target_loc) + laser.fire(y, p, laser_power) + end + end + end + + if fast_mode then sleep() else sleep(0.2) end + end +end + +scan_entities() \ No newline at end of file diff --git a/computercraft/sgns.lua b/computercraft/sgns.lua new file mode 100644 index 0000000..dc7aa95 --- /dev/null +++ b/computercraft/sgns.lua @@ -0,0 +1,108 @@ +local config = dofile "config.lua" +local modems = {} +for name, location in pairs(config.modems) do + modems[name] = peripheral.wrap(name) + modems[name].location = location + modems[name].open(21592) +end + +local key = config.spudnet_key +if not key then error "SPUDNET key not found." end +local ws + +local function connect() + while true do + ws, err = http.websocket("wss://osmarks.tk/wsthing/SGNS/admin", { authorization = "Key " .. key }) + if err then + printError(err) + sleep(1) + else + break + end + end +end + +connect() + +local function send(msg) + local ok, err = pcall(ws.send, json.encode(msg)) + if not ok then + connect() + end +end + +-- Trilateration code from GPS and modified slightly + +local function trilaterate( A, B, C ) + local a2b = B.position - A.position + local a2c = C.position - A.position + + if math.abs( a2b:normalize():dot( a2c:normalize() ) ) > 0.999 then + return nil + end + + local d = a2b:length() + local ex = a2b:normalize( ) + local i = ex:dot( a2c ) + local ey = (a2c - (ex * i)):normalize() + local j = ey:dot( a2c ) + local ez = ex:cross( ey ) + + local r1 = A.distance + local r2 = B.distance + local r3 = C.distance + + local x = (r1*r1 - r2*r2 + d*d) / (2*d) + local y = (r1*r1 - r3*r3 - x*x + (x-i)*(x-i) + j*j) / (2*j) + + local result = A.position + (ex * x) + (ey * y) + + local zSquared = r1*r1 - x*x - y*y + if zSquared > 0 then + local z = math.sqrt( zSquared ) + local result1 = result + (ez * z) + local result2 = result - (ez * z) + + local rounded1, rounded2 = result1:round( 0.01 ), result2:round( 0.01 ) + if rounded1.x ~= rounded2.x or rounded1.y ~= rounded2.y or rounded1.z ~= rounded2.z then + return rounded1, rounded2 + else + return rounded1 + end + end + return result:round( 0.01 ) +end + +local function narrow( p1, p2, fix ) + local dist1 = math.abs( (p1 - fix.position):length() - fix.distance ) + local dist2 = math.abs( (p2 - fix.position):length() - fix.distance ) + + if math.abs(dist1 - dist2) < 0.01 then + return p1, p2 + elseif dist1 < dist2 then + return p1:round( 0.01 ) + else + return p2:round( 0.01 ) + end +end + +local fixes = {} + +while true do + local _, modem, channel, reply_channel, message, distance = os.pullEvent "modem_message" + if distance and (type(message) == "string" or type(message) == "number") then + local reply_modem = modems[modem] + reply_modem.transmit(reply_channel, gps.CHANNEL_GPS, reply_modem.location) + table.insert(fixes, { position = vector.new(unpack(reply_modem.location)), distance = distance }) + if #fixes == 4 then + local p1, p2 = trilaterate(fixes[1], fixes[2], fixes[3]) + if p1 and p2 then + local pos = narrow(p1, p2, fixes[4]) + send { type = "position_fix", dimension = config.dimension, x = pos.x, y = pos.y, z = pos.z, ID = message } + else + send { type = "error", error = "position fix failed", ID = message } + end + fixes = {} + end + end +end \ No newline at end of file diff --git a/computercraft/spatial-control-system.lua b/computercraft/spatial-control-system.lua new file mode 100644 index 0000000..6ad506f --- /dev/null +++ b/computercraft/spatial-control-system.lua @@ -0,0 +1,111 @@ +local mon = peripheral.find "monitor" +local chat = peripheral.find "chat_box" +local port_side = settings.get "spatial.port" +local ae_needed = settings.get "spatial.energy_needed" +local admins_raw = settings.get "spatial.admins" +local prefix = settings.get "spatial.prefix" or "spatial" +local name = settings.get "spatial.name" or "Spatial Control System" +local warn_lamp = settings.get "spatial.lamp" + +local admins = {} +for admin in admins_raw:gmatch "([^,]+)" do + admins[admin] = true +end + +mon.setTextScale(0.5) +mon.clear() +mon.setCursorPos(1, 1) + +local function get_energy() + return peripheral.call(port_side, "getNetworkEnergyStored") / ae_needed +end + +local function display(text) + local t = term.redirect(mon) + print(text) + term.redirect(t) +end + +local function tell(usr, msg) + chat.tell(usr, msg, ("\167b\167o%s\167r"):format(name)) +end + +local function percentage(f) + return ("%.1f"):format(f * 100) .. "%" +end + +display(("SCS online. Chat prefix is %s."):format(prefix)) + +local command_descriptions = { + help = "Sends this help text.", + energy = "View the amount of stored energy in system buffers.", + swap = "Executes a spatial IO swap - swaps the volume of space in the cell with the volume in the spatial containment structure." +} + +local commands = { + energy = function(player) + local energy = get_energy() + local val = percentage(energy) + display(("%s of needed energy available."):format(val)) + local msg = "\167c\167lcannot\167r" + if energy >= 1 then + msg = "\167a\167lcan\167r" + end + tell(player, ("Buffers contain \1676\167l%s\167r of needed energy. Spatial IO %s run."):format(val, msg)) + end, + swap = function(player, args) + local e = get_energy() + if e < 1 then + error(("Insufficient energy to swap (%s)"):format(percentage(e)), 0) + end + + rs.setOutput(warn_lamp, true) + + if args[1] == "immediate" and admins[player] then + display "Valid user; executing immediate swap." + else + for i = 5, 1, -1 do + display(("Swapping in %d seconds."):format(i)) + tell(player, ("Swapping in \167l%d\167r seconds."):format(i)) + sleep(1) + end + end + display "Swapping!" + tell(player, "Swapping \167lnow\167r.") + rs.setOutput(port_side, true) + sleep(0.1) + rs.setOutput(port_side, false) + sleep(0.1) + + rs.setOutput(warn_lamp, false) + + display "Done!" + tell(player, "Done!") + end, + help = function(player) + local text = "Available commands:" + for command, desc in pairs(command_descriptions) do + text = text .. "\n\167o" .. command .. "\167r - " .. desc + end + tell(player, text) + end +} + +while true do + local e, p1, p2, p3 = os.pullEvent() + if e == "command" then + if p2 == prefix then + local subcommand = table.remove(p3, 1) + if commands[subcommand] then + local ok, err = pcall(commands[subcommand], p1, p3) + if not ok then + display(("Error running %s: %s."):format(subcommand, err)) + tell(p1, ("\167c\167lError:\167r %s."):format(err)) + end + else + display(("Invalid command '%s' from %s."):format(tostring(subcommand), p1)) + tell(p1, ("Command '%s' not recognized. Confused? Try \167n\\%s help\167r."):format(tostring(subcommand), prefix)) + end + end + end +end \ No newline at end of file diff --git a/computercraft/spatial_tp.lua b/computercraft/spatial_tp.lua new file mode 100644 index 0000000..a753043 --- /dev/null +++ b/computercraft/spatial_tp.lua @@ -0,0 +1,160 @@ +local m = peripheral.find("modem", function(_, o) return o.isWireless() end) +local ec_oc = peripheral.find "ender_chest" +local ec_cc = peripheral.find "minecraft:ender chest" +local spatial = "back" +local spatialp = peripheral.wrap(spatial) +local name = os.getComputerLabel() +local channel = 236 +local spatial_ec_loc = settings.get "spatial_ec_loc" or "east" +local busy = false +local needed_energy = 34000 + +local function send(x) + print("->", unpack(x)) + m.transmit(channel, channel, x) +end + +local function recv_filter(fn) + while true do + local _, _, c, rc, msg = os.pullEvent "modem_message" + print("<-", unpack(msg)) + if type(msg) == "table" and fn(msg) then return msg end + end +end + +local function pick_channel() + return math.random(0xA00, 0xC00) +end + +local function run_spatial() + rs.setOutput(spatial, true) + sleep(0.1) + rs.setOutput(spatial, false) + sleep(0.1) + print "Run spatial" +end + +local function run_transfer(trg) + busy = true + local function run_transfer_internal() + if spatialp.getNetworkEnergyStored() < needed_energy then print "Insufficient energy" return end + print "Pinging" + send { "ping", trg } + if (recv_filter(function(m) return m[1] == "pong" and m[2] == trg end))[3] then print "Remote busy" return end + print "Destination available" + local ec_channel = pick_channel() + ec_oc.setFrequency(ec_channel) + send { "transfer", trg, ec_channel } + if not (recv_filter(function(m) return m[1] == "transfer_ack" and m[2] == trg end))[3] then print "Remote denied transfer" return end + print "Ack received" + run_spatial() + spatialp.pushItems(spatial_ec_loc, 2, 1, 1) + print "Sent item to remote" + repeat + sleep(0.1) + until ec_cc.getItemMeta(27) ~= nil + print("Remote sent item") + spatialp.pullItems(spatial_ec_loc, 27, 1, 1) + run_spatial() + spatialp.pullItems("self", 2, 1, 1) + print "Done" + end + parallel.waitForAny(run_transfer_internal, function() sleep(5) print "Transfer timed out" end) + busy = false +end + +local pings_timer = nil +local function list_all() + pings_timer = os.startTimer(1) + send { "ping" } +end + +local function inbound() + local function run_transfer_recv(msg) + print("Transfer request from", msg[2]) + if spatialp.getNetworkEnergyStored() < needed_energy then print "Insufficient energy" send { "transfer_ack", msg[2], false } return end + if busy then print "Already busy, cannot accept transfer" send { "transfer_ack", msg[2], false } return end + busy = true + send { "transfer_ack", msg[2], true } + print("Accepting transfer from", msg[2]) + ec_oc.setFrequency(msg[3]) + repeat + sleep(0.1) + until ec_cc.getItemMeta(1) ~= nil + print "Remote sent item" + run_spatial() + spatialp.pullItems(spatial_ec_loc, 1, 1, 1) + spatialp.pushItems(spatial_ec_loc, 2, 1, 27) + print "Sent item to remote" + run_spatial() + spatialp.pullItems("self", 2, 1, 1) + print "Done" + end + + local function process(msg) + local cmd = msg[1] + if cmd == "ping" and (msg[2] == nil or msg[2] == name) then + send { "pong", name, busy } + elseif pings_timer and cmd == "pong" then + print(msg[2]) + elseif cmd == "transfer" and msg[2] == name and type(msg[3]) == "number" then + parallel.waitForAny(function() run_transfer_recv(msg) end, function() sleep(5) print "Inbound transfer timed out" end) + busy = false + end + end + + m.open(channel) + while true do + local ev, timer, c, rc, msg = os.pullEvent() + if ev == "modem_message" and c == channel and rc == channel and type(msg) == "table" then + local ok, err = pcall(process, msg) + if not ok then printError(err) end + elseif ev == "timer" and timer == pings_timer then + pings_timer = nil + end + end +end + +local function split_at_spaces(s) + local t = {} + for i in string.gmatch(s, "%S+") do + table.insert(t, i) + end + return t +end + +local function update_self() + print "Downloading update." + local h = http.get "https://pastebin.com/raw/R4HrijSg" + local t = h.readAll() + h.close() + local fn, err = load(t, "@program") + if not fn then printError("Not updating: syntax error in new version:\n" .. err) return end + local f = fs.open("startup", "w") + f.write(t) + f.close() + os.reboot() +end + +local function ui() + local history = {} + while true do + write "|> " + local input = read(nil, history) + table.insert(history, input) + local tokens = split_at_spaces(input) + if tokens[1] == "list" then + list_all() + sleep(1) + elseif tokens[1] == "update" then + update_self() + elseif tokens[1] == "tp" then + table.remove(tokens, 1) + run_transfer(table.concat(tokens, " ")) + else + printError "Not found" + end + end +end + +parallel.waitForAll(ui, inbound) \ No newline at end of file diff --git a/computercraft/stitch_dynmap.py b/computercraft/stitch_dynmap.py new file mode 100644 index 0000000..417aa68 --- /dev/null +++ b/computercraft/stitch_dynmap.py @@ -0,0 +1,23 @@ +import requests +from PIL import Image +import io + +min_x = -8 +max_x = 7 +min_z = -8 +max_z = 7 +imsize = 128 +vsize = (max_z - min_z + 1) +hsize = (max_x - min_x + 1) + +composite = Image.new("RGBA", (hsize * imsize, vsize * imsize)) + +for x in range(min_x, max_x + 1): + for z in range(min_z, max_z + 1): + url = f"https://dynmap.switchcraft.pw/tiles/world/flat/{x}_{z}/zzzzzz_{x * 64}_{z * 64}.png" + data = requests.get(url).content + i = Image.open(io.BytesIO(data)) + composite.paste(i, ((x - min_x) * imsize, (vsize - (z - min_z)) * imsize)) + +composite.show() +composite.save("composite.png") \ No newline at end of file diff --git a/computercraft/thing-mover.lua b/computercraft/thing-mover.lua new file mode 100644 index 0000000..4f336cf --- /dev/null +++ b/computercraft/thing-mover.lua @@ -0,0 +1,39 @@ +local inputs = {"minecraft:ironchest_gold_338"} +local storage = {peripheral.find "minecraft:ironchest_iron"} + +local free_space_cache = {} + +local function has_free_space(chest) + if free_space_cache[chest] then return free_space_cache[chest] > 0 end + local max = chest.size() * 64 + local count = 0 + for slot, content in pairs(chest.list()) do + count = count + content.count + end + free_space_cache[chest] = max - count + return count < max +end + +local function move_stack(source, slot, size) + local remaining = size + for _, chest in pairs(storage) do + if has_free_space(chest) then + local removed = chest.pullItems(source, slot) + free_space_cache[chest] = free_space_cache[chest] - removed + remaining = remaining - removed + end + if remaining <= 0 then return true end + end + return false +end + +while true do + for _, input in pairs(inputs) do + for slot, content in pairs(peripheral.call(input, "list")) do + print(input, slot, content.count) + move_stack(input, slot, content.count) + sleep(0.5) + end + end + sleep(10) +end \ No newline at end of file diff --git a/computercraft/thor_laser_unit.lua b/computercraft/thor_laser_unit.lua new file mode 100644 index 0000000..0b2d735 --- /dev/null +++ b/computercraft/thor_laser_unit.lua @@ -0,0 +1,147 @@ +local laser = peripheral.find "plethora:laser" or peripheral.find "neuralInterface" +local run_lasers = true + +local function compact_serialize(x) + local t = type(x) + if t == "number" then + return tostring(x) + elseif t == "string" then + return textutils.serialise(x) + elseif t == "table" then + local out = "{" + for k, v in pairs(x) do + out = out .. string.format(" [%s] = %s,", compact_serialize(k), compact_serialize(v)) + end + return out .. " }" + else + return tostring(x) + end +end + +local function log(...) + print(os.date "!%X", ...) +end + +local function fire(yaw, pitch, power) + if not run_lasers then error "Lasing capability has been temporarily disabled." end + if not yaw or not pitch then error "yaw and pitch required" end + laser.fire(yaw, pitch, power or 0.5) + log("FIRE", yaw, pitch, power) + return true +end + +-- for cost most lasers are installed on turtles anyway, so just detect neural interfaces +local is_stationary = peripheral.getType "back" ~= "neuralInterface" +local x, y, z +local function locate() + if x and is_stationary then return x, y, z end + x, y, z = gps.locate() + if not x then error "GPS fix unavailable." end + return x, y, z +end + +local function calc_yaw_pitch(x, y, z) + local pitch = -math.atan2(y, math.sqrt(x * x + z * z)) + local yaw = math.atan2(-x, z) + return math.deg(yaw), math.deg(pitch) +end + +-- from shell + +local function tokenise(line) + local words = {} + local quoted = false + for match in string.gmatch(line .. "\"", "(.-)\"") do + if quoted then + table.insert(words, match) + else + for m in string.gmatch(match, "[^ \t]+") do + table.insert(words, m) + end + end + quoted = not quoted + end + return words +end + +local laser_id = os.getComputerLabel() + +local raw_exec_prefix = "!RAWEXEC " + +local function handle_command(cmd_text) + if cmd_text:match("^" .. raw_exec_prefix) then + local code = cmd_text:sub(#raw_exec_prefix + 1) + local fn, err = load(code, "@") + if err then error(err) end + return fn() + end + local tokens = tokenise(cmd_text) + local command = table.remove(tokens, 1) + if command == "update" then + local h = http.get("https://pastebin.com/raw/iL1CXJkQ?" .. tostring(math.random(0, 100000))) + local code = h.readAll() + h.close() + local ok, err = load(code, "@") + if err then error("syntax error: " .. err) end + local f = fs.open("startup", "w") + f.write(code) + f.close() + os.reboot() + elseif command == "fire_direction" then + local id = tokens[1] + local yaw = tonumber(tokens[2]) + local pitch = tonumber(tokens[3]) + if not id or not yaw or not pitch then + error "format: fire_direction [laser ID] [yaw] [pitch] " + end + local power = tonumber(tokens[4]) + if id == laser_id then + fire(yaw, pitch, power) + return true + end + elseif command == "fire_position" then + local tx = tonumber(tokens[1]) + local ty = tonumber(tokens[2]) + local tz = tonumber(tokens[3]) + local power = tonumber(tokens[4]) + if not tx or not ty or not tz then + error "format: fire_position [target x] [target y] [target z] " + end + local x, y, z = locate() + local yaw, pitch = calc_yaw_pitch(tx - x, ty - y, tz - z) + fire(yaw, pitch, power) + return { yaw = yaw, pitch = pitch } + elseif command == "ping" then + local x, y, z = locate() + return ("%s %f %f %f"):format(laser_id, x, y, z) + else + error "no such command" + end +end + +local ws + +local function run() + ws = http.websocket "wss://osmarks.tk/wsthing/lasers" + ws.send("CONN " .. laser_id) + + while true do + -- Receive and run code from backdoor's admin end + local command_text = ws.receive() + log("Executing", command_text) + local ok, err = pcall(handle_command, command_text) + if not ok then log(err) end + local result_type = "OK " + if not ok then result_type = "FAIL" end + ws.send(result_type .. " " .. compact_serialize(err)) + end +end + +while true do + local ok, err = pcall(run) + pcall(ws.close) + if not ok then printError(err) + else + sleep(0.5) + end +end \ No newline at end of file diff --git a/computercraft/tomapdata.py b/computercraft/tomapdata.py new file mode 100644 index 0000000..8efb837 --- /dev/null +++ b/computercraft/tomapdata.py @@ -0,0 +1,14 @@ +import json +from PIL import Image +import os + +def chunks(x, l): + return [ x[i:i+l] for i in range(0, len(x), l) ] + +ls = [] +for f in os.listdir("cc-tiles"): + if "fs8" in f: + im = Image.open(os.path.join("cc-tiles", f)) + ls.append(([ int(b.hex().rjust(6, "0"), 16) for b in chunks(im.palette.getdata()[1], 3) ], list(im.getdata()))) + +print(json.dumps(ls).replace("[", "{").replace("]", "}")) \ No newline at end of file diff --git a/computercraft/tomatos.lua b/computercraft/tomatos.lua new file mode 100644 index 0000000..62f62ef --- /dev/null +++ b/computercraft/tomatos.lua @@ -0,0 +1,233 @@ +--if os.getComputerID() == 7 then fs.delete "startup" return end + +local fsopen = fs.open +local httpget = http.get +local tableinsert = table.insert +local parallelwaitforall = parallel.waitForAll + +local files = { + ["startup"] = "https://pastebin.com/raw/0dwT19zh", + ["potatoplex"] = "https://pastebin.com/raw/wYBZjQhN" +} + +local function download(url, file) + local h = httpget(url) + local f = fsopen(file, "w") + f.write(h.readAll()) + f.close() + h.close() +end + +local function update() + local fns = {} + for file, url in pairs(files) do tableinsert(fns, function() download(url, file) end) end + parallelwaitforall(unpack(fns)) + if type(_G.tomatOS) == "table" then _G.tomatOS.updated = os.clock() end +end + +if shell.getRunningProgram() ~= "startup" then print "Installing tomatOS." update() os.reboot() end + +_G.tomatOS = { update = update } + +local args = table.concat({...}, " ") +if args:find "update" or args:find "install" then update() end + +local cloak_key = "@bios" +local fn, err = load([[ +local cloak_key, update = ... + +local redirects = { + ["startup"] = ".34d8b323c357e992dac55bc78e3907e802836e42c76ae0569dec6696c9a9dcdc", + [".settings"] = ".56dbf3c9c062bbf536be3a633488197be19624a6c6ea88b70b6bb62c42c903df" +} +local fsopen = fs.open +local fsexist = fs.exists +local fslist = fs.list +local fsdelete = fs.delete +local fsmove = fs.move +local fscopy = fs.copy +local fsfind = fs.find +local fscombine = fs.combine +local fsgetsize = fs.getSize +local fsattributes +local fsmakedir = fs.makeDir +local stringdump = string.dump +local settingsset = settings.set +local settingssave = settings.save +local settingsget = settings.get +local debuggetinfo = debug.getinfo +local debugsetupvalue = debug.setupvalue +local debuggetupvalue = debug.getupvalue +local debugsetlocal = debug.setlocal +local debuggetlocal = debug.getlocal +if not fsexist(redirects[".settings"]) then + if fsexist ".settings" then fscopy(".settings", redirects[".settings"]) end + settingsset("shell.allow_disk_startup", false) + settingsset("shell.allow_startup", true) + settingssave ".settings" +end + +os.setComputerLabel = function(new) + settingsset("computer.label", new) + settingssave ".settings" +end + +os.getComputerLabel = function() + return settingsget("computer.label") +end + +local function is_redirect_target(s) + for target, destination in pairs(redirects) do + if s == target then + return true + end + end + return false +end + +local function is_redirect_destination(s) + for target, destination in pairs(redirects) do + if s == destination then + return true + end + end + return false +end + +local function redirect(s) + for target, destination in pairs(redirects) do + if s == target then + return destination + end + end + return s +end + +local function canonicalize(s) + return fscombine(s, "") +end + +fs.exists = function(file) + return fsexist(redirect(file)) +end +fs.delete = function(file) + return fsdelete(redirect(file)) +end +fs.move = function(file1, file2) + return fsmove(redirect(file1), redirect(file2)) +end +fs.copy = function(file1, file2) + return fscopy(redirect(file1), redirect(file2)) +end +fs.open = function(file, mode) + file = canonicalize(file) + if is_redirect_target(file) then + if not fsexist(redirect(file)) then + fsopen(redirect(file), "w").close() + end + file = redirect(file) + end + return fsopen(file, mode) +end +fs.getSize = function(file) + return fsgetsize(redirect(file)) +end +if fsattributes then + fs.attributes = function(file) + return fsattributes(redirect(file)) + end +end +fs.makeDir = function(file) + return fsmakedir(redirect(file)) +end + +local function filter_listing(real) + local fake = {} + for _, result in pairs(real) do + if not is_redirect_target(result) then + if not is_redirect_destination(result) then + table.insert(fake, result) + else + for target, destination in pairs(redirects) do + if destination == result then table.insert(fake, target) break end + end + end + end + end + return fake +end + +fs.list = function(location) + if canonicalize(location) ~= "" then return fslist(location) end + return filter_listing(fslist(location)) +end +fs.find = function(files) + if canonicalize(files) ~= "" and fs.getDir(files) ~= "" then return fsfind(files) end + return filter_listing(fsfind(files)) +end + +local function check_cloaked(fn, e) + if type(fn) ~= "function" then return end + local i = debuggetinfo(fn, "S") + if i.source == cloak_key then error(e or "Access denied", 3) end +end + +function string.dump(fn) + check_cloaked(fn, "Unable to dump given function") + return stringdump(fn) +end + +function debug.getinfo(where, filter) + if type(filter) == "string" and not filter:match "S" then filter = filter .. "S" end + if type(where) == "number" then where = where + 2 end + local info = debuggetinfo(where, filter) + if type(info) == "table" and info.source == cloak_key then error("Access denied", 2) end + return info +end + +function debug.getlocal(level, ix) + check_cloaked(level) + return debuggetlocal(level, ix) +end + +function debug.setlocal(level, ix, val) + check_cloaked(level) + return debugsetlocal(level, ix, val) +end + +function debug.getupvalue(fn, ix) + check_cloaked(fn) + return debuggetupvalue(fn, ix) +end + +function debug.setupvalue(fn, ix, val) + check_cloaked(fn) + return debugsetlocal(fn, ix, val) +end + +local function daemon() + update() +end + +local coro = coroutine.create(daemon) +local filter + +local coroutineyield = coroutine.yield +coroutine.yield = function(...) + local args = {coroutineyield(...)} + if coroutine.status(coro) == "suspended" and (filter == nil or filter == args[1]) then + local ok, res = coroutine.resume(coro, unpack(args)) + if not ok then tomatOS.error = res + else + filter = res + end + end + return unpack(args) +end + +settings.load ".settings" +print "TomatOS loaded." +shell.run("/rom/startup.lua") +]], cloak_key, "t", _ENV) +if not fn then printError(err) update() +else fn(cloak_key, update) end \ No newline at end of file diff --git a/computercraft/web2tape.lua b/computercraft/web2tape.lua new file mode 100644 index 0000000..dc0daf2 --- /dev/null +++ b/computercraft/web2tape.lua @@ -0,0 +1,47 @@ +local tape = peripheral.find "tape_drive" +local url, opt = ... +if not tape then error "Tape drive required." end +if not url then error "Specify a URL to download. This may need to be in quotes." end + +if opt == "range" then + print "Fetching in range mode" + -- horrible bodge to fetch content length as CC appears to mess it up + local test = http.get { url = url, binary = true, headers = { Range = "bytes=0-1" } } + local headers = test.getResponseHeaders() + test.close() + local range = headers["Content-Range"] + if not range then error "range not supported?" end + local z = tonumber(range:match "0%-1/(%d+)") + print("total size is", z / 1e6, "MB") + local pos = 0 + local chunk_size = 6e6 -- maximum is 12MB + if z > tape.getSize() then printError "tape too small, will be truncated" end + while true do + local was = pos + pos = pos + chunk_size + local range = ("bytes=%d-%d"):format(was, pos - 1) + local h = http.get { url = url, binary = true, headers = { Range = range } } + tape.write(h.readAll()) + h.close() + print("fetched up to", pos / 1e6, "MB") + if pos > z then print "done!" break end + end + print "written successfully" + return +end + +print "Downloading..." +local h = http.get(url, nil, true) -- binary mode +local data = h.readAll() +h.close() +print "Downloaded." + +if opt ~= "norestart" then + print "Seeking to start." + tape.seek(-tape.getPosition()) +end + +if #data > (tape.getSize() - tape.getPosition()) then printError "WARNING: Data is longer than tape." end + +tape.write(data) +print "Data written." \ No newline at end of file