Jump to content
  • Sky
  • Blueberry
  • Slate
  • Blackcurrant
  • Watermelon
  • Strawberry
  • Orange
  • Banana
  • Apple
  • Emerald
  • Chocolate
  • Charcoal


  • Content Count

  • Joined

  • Last visited

  • Days Won


Fingercomp last won the day on March 29

Fingercomp had the most liked content!


About Fingercomp

  • Rank
    Leading Member

Profile Information

  • Gender

Contact Methods

  • GitHub
  • IRC

Recent Profile Visitors

2429 profile views
  1. If you're running OpenOS, there's a proper way to disable hard interrupts: require("process").info().data.signal = function() end. This function is the interrupt handler, and by populating the body of the function, you can define the desired behavior when Ctrl-Alt-C is pressed. Though the pcall solution makes sense if you want to restart the program when it crashes (because of a bug, for instance). For the sake of completeness, soft interrupts, Ctrl-C without the Alt key, generate an interrupted event.
  2. Let's solve the problem iteratively, one step at a time. The first thing to do is to define the task thoroughly. Our goal is to make a program that checks periodically if there's an authorized person, opens the door if there is one, and closes it otherwise. Then we should require everything we need — that is, the door and the RFID reader. local door = require("component").os_door local rfid = require("component").os_rfidreader This is almost the same as what you did in your code, but notice that the variables I've defined are local. You may wonder what it means and why I'm doing this. I'm not going to answer the former question here, lest the answer be too long to read without feeling bored. Instead, I'll answer the latter question. Making every variable local helps to prevent odd bugs with "ghost" variables (those that possess a value, albeit not being set anywhere in the program), complicating program development needlessly, and serves to urge the programmer to pass data between functions explicitly instead of relying on global variables. Though, if I am to be honest, it won't make a difference in this program. Let's move on. The program needs to know the nick of the authorized user. local authorized = "johnsmith" Now we'll need the program to repeat some actions (checking surroundings, managing the door) indefinitely. For that we use a loop — specifically, an infinite loop. while true do ... end The ellipsis is a placeholder for the loop body, which will be repeated. Let's replace it with actual code. First, perform a scan. local scan = rfid.scan() scan is now a table of tables. Each subtable contains information about one player. Because we don't know which subtable corresponds to the authorized person, we have to check every entry of scan. We'll use a for loop. local shouldOpen = false for _, player in ipairs(scan) do ... end player is a table. The nick of each player is stored in the field name. We can access its value with player.name. If it's equal to authorized, we'll set the shouldOpen variable to true. Otherwise, we look further. If that person is not detected by the scanner, shouldOpen will remain false. for _, player in ipairs(scan) do if player.name == authorized then shouldOpen = true break end end If we find the user, the break statement stops the for loop prematurely. Also note that we don't need to put local before shouldOpen = true: local defines a new variable, whereas we just want to reassign an existing one. Now that we know if there's a trusted user nearby, we can control the door. if shouldOpen then door.open() else door.close() end If the door is already open, door.open() does nothing (likewise if we .close() a closed door), which is what we want. Finally, we add an os.sleep to wait a second before checking again. os.sleep(1) Gathering the snippets together, we get the finished program. local door = require("component").os_door local rfid = require("component").os_rfidreader local authorized = "johnsmith" while true do local scan = rfid.scan() local shouldOpen = false for _, player in ipairs(scan) do if player.name == authorized then shouldOpen = true break end end if shouldOpen then door.open() else door.close() end os.sleep(1) end
  3. I had this problem quite a long time ago, and I think I fixed it by also updating the settings client-side to match those on the server.
  4. There was no IRC library for OpenComputers, so I've made one. Here's a demo bot that uses it: local com = require("component") local event = require("event") local thread = require("thread") local gpu = com.gpu local irc = require("irc") local events = irc.events local env = setmetatable({ irc = irc, events = events, }, {__index = _G}) local client = irc.builder() :connection { host = "irc.esper.net:6667", throttling = { maxDelay = 2, maxThroughput = 5, }, } :auth { nickname = "oc-finger-irc", username = "fingercomp", realname = "OpenComputers IRC client library", } :bot { channels = {"#oc-finger-irc"}, tracking = { users = true, modes = true, account = true, userInfo = true, }, } :execution { threaded = true, reconnect = true, catchErrors = true, } :subscribe(events.irc.command, events.priority.high, function(self, client, evt) gpu.setForeground(0x00ff00) print("→ " .. evt.rawLine) gpu.setForeground(0xffffff) end) :subscribe(events.irc.write, events.priority.normal, function(self, client, evt) gpu.setForeground(0x00dbff) print("← " .. evt.line:gsub("[\r\n]*$", "")) gpu.setForeground(0xffffff) end) :subscribe(events.irc.message, irc.events.priority.normal, function(self, client, evt) if evt.source.nickname == "fingercomp" then if evt.message == "::quit" then evt:reply("Quitting.") evt.client:stop(("%s told me to quit."):format(evt.source.nickname)) elseif evt.message == "::spam" then evt:reply("1") evt:reply("2") evt:reply("3") evt:reply("4") evt:reply("5") elseif evt.message == "::longmsg" then local msg = {} for i = 1, 256 do if i == 128 then table.insert(msg, tostring(i) .. " ") else table.insert(msg, tostring(i)) end end evt:reply(table.concat(msg)) elseif evt.message == "::error" then (nil).test() elseif evt.message:sub(1, #"::exec ") == "::exec " then local code = evt.message:sub(#"::exec " + 1) local chunk, reason = load("return " .. code, "=irc", "t", env) if not chunk then chunk, reason = load(code, "=irc", "t", env) end if not chunk then evt:reply(("\x0304Error:\x0f %s"):format(reason)) else local result = table.pack(xpcall(chunk, debug.traceback)) local success = table.remove(result, 1) result.n = result.n - 1 for i = 1, result.n, 1 do if type(result) ~= "string" and type(result) ~= "number" and type(result) ~= "boolean" and type(result) ~= "nil" then result[i] = tostring(result[i]) else result[i] = ("%q"):format(result[i]):gsub("\\\n", "\n") end end if not success then evt:reply(("\x0304Error:\x0f %s"):format(result[1]:match("^[^\n]*"))) io.stderr:write(("%s\r\n"):format(result[1])) elseif result.n > 0 then evt:reply(table.concat(result, " ", i, result.n)) else evt:reply("\x0309Success") end end end end end) :subscribe(events.irc.ctcpRequest, irc.events.priority.normal, function(self, client, evt) if evt.ctcpCommand == "TIME" then evt:reply(os.date("%F %T")) end end) :subscribe(events.client.error, irc.events.priority.top, function(self, client, evt) print("Caught: " .. evt.traceback) evt:cancel() end) :build() local t = thread.create(function() repeat local evt = event.pull("interrupted") until evt end) env.client = client client:run() thread.waitForAny({t, client.thread}) client:stop("Quitting.") os.exit() I don't want to brag about it too much, but I think the code is pretty. Features Threaded execution. A nice API. IRCv3.2 capability negotiation. Throttling to prevent the bot from triggering anti-flood mechanisms. Tracks channel members, modes, nick changes, account names, etc. Automatically reconnects if it's disconnected from the IRC server. Splits long messages over multiple lines. The event bus can use coroutines as message handlers. Links The GitLab repository: https://gitlab.com/cc-ru/irc-oc Documentation: https://gitlab.com/cc-ru/irc-oc/-/wikis/home Demo programs: https://gitlab.com/cc-ru/irc-oc/snippets
  5. TL;DR: set the resolution to 159×28. Displays automatically scale the content to fit the screen's inner area. If you decrease the resolution height, the display area will occupy more horizontal space. I'll assume that your screen is 8×3. The aspect ratio of the screen's inner area is (2 × (8×16 - 4.5)) : (3×16 - 4.5) = 494×87. The "- 4.5" terms are the screen borders, and the width is doubled because the cell width is half its height. If you set the resolution proportional to this ratio, it will fill the whole screen. Of course, you can't do this, as the maximum resolution is 160×50. So we have to compromise and choose the resolution that fits the screen the best. local function round(num) local integer, frac = math.modf(num) if frac >= 0.5 then integer = integer + 1 end return integer end local data = {} for w = 1, 160, 1 do local h = math.max(math.min(round(w * 87 / 494), 160 * 50 / w, 160), 1) local area = w * h local delta = (w / h) - 494 / 87 table.insert(data, {delta, area, ("%d×%d"):format(w, h)}) end table.sort(data, function(lhs, rhs) return lhs[1] < rhs[1] end) local f = io.open("./data", "w") for k, v in ipairs(data) do f:write(("%.6f %d %s\n"):format(v[1], v[2], v[3])) end f:close() The above code creates an array and fills it with 160 possible resolutions by choosing a width and calculating the height such that the aspect ratio is the closest to what we're trying to achieve (494:87). Then it writes the data to a file so that we can plot it. Here's what we get: Now it's clear that the best resolution is 159×28. Its aspect ratio differs from 494/87 by ~0.000411, which is really small. Let's go ahead and calculate the thickness of the black area. Let dw, dh be the dimensions of the display area, and iw, ih the dimensions of the inner area. dw/dh = 159/28; iw/ih = 494/87. 159/28 > 494/87, so the display area height is less than the screen's inner area height. Therefore, the width of the black area is dw = iw. The height equals 0.5 × (ih - dh) = 0.5 × (87/494 × iw - 28/159 × dw) = 0.5 × iw × (87/494 - 28/159) = 0.5 × 494/87 × ih × (87/494 - 28/159) = 1/27666 × ih. In other words, the height of the black area is 1/27666th of the whole inner area height, which is negligible.
  6. Pressing the button that is placed on the screen turns the screen on/off. Make sure the button is not adjacent to the screen. Also, you should use rs.getInput in yor problem. rs.getOutput returns the strength of the redstone signal emitted by the computer's redstone card.
  7. Fingercomp

    Drone usage?

    Of course there is. Assembling a drone with a leash upgrade produces a drone that can transport cows, horses, sheeps, and pigs. Like this: Or... like this: Besides, drones are entities. They move in straight lines; they don't get stuck in the fence and can actually enter a house through a door. They move pretty fast — about 6 blocks a second (robots are at least 2 times slower). Drone's movement is asynchronous, so the machine can execute some other code in the middle of the flight. Drones can fly above the block height limit. The screenshot above was made while enjoying the flight at a height of 260 blocks, for example. Oh, they can fly below the bedrock, too. Also, drones look cool. Jobs at which drones clearly outperform robots include player transportation (thanks to the leash upgrade) and item transportation (due to their mobility). And, if used with robots, drones can be useful for crop farming, autocrafting, and construction, for example.
  8. It's a bit unclear for me what kind of problem you have. Is print also called twice per supposedly one sent modem message? Or is it only the doHarvestRoutine call that's repeated? If it's the former case (the print line is also run twice), it means your sent messages are somehow duplicated. First thing to check for is whether you have any relays nearby. These blocks LOVE to mess up with network packets. If you use them, I advice you to remove them and use linked cards or a network that checks for duplicates. If you don't, it's very likely that the sender program has a bug that causes it to send each packet twice.
  9. The last chapter mentions briefly the biggest problems that make sound reconstructed from the audio data sound bad, and I'm going to try to explain them. 1. The program is quite simple. It's merely a proof of concept. It doesn't use modulation or non-sine waveforms. 2. Most sounds are complex. Most sounds I hear are far from being purely sinusoidal. Perhaps it's different for you, or for someone else who happens to read this post. :) White noise is vastly different from a flute, and the flute isn't the same as a guitar. Each instrument has a unique timbre, and even a single instrument can have different timbres depending how performers play it. Sound can be decomposed into an infinite number of sinusoids that, when summed together, produce the original sound. This is what Fourier transform does, basically. So if you use it to decompose a sound of a violin playing, and filter the output, you'll see, indeed, multiple sine waves that construct the signal, which are called partials. When you reconstruct the sound back, to get better approximation, you need to sum more partials. 3. Unfortunately, the sound card only has 8 channels (by default). This means that from the set of sinusoids that represent the encoded signal we have to choose eight of them. It's enough for a piano, but for some sounds (such as human speech) it's clearly too few of them, and what you get doesn't even seem to be similar to what was originally encoded. It's worth noting that if the program also analyzed the spectrum to use different waveforms, it could probably achieve a better resemblance. 4. Furthermore, we discard phase. The Fourier transform produces two values for each sinusoid: the amplitude, and the phase. The phase is important. For example, if you have two sine waves that differ in phase by π, and have the same frequency and amplitude, you won't hear any sound. These sinusoids are opposite of each other: if, at some point, one has a value of 0.5, the other has the value of -0.5, so, when you sum them, you get zero everywhere. The sound card has no way of setting phase. It's possible to get the situation as described above: if the first channel accumulates phase of π, and then you initialize the second channel, you may hear no sound at all! Though, given the limited channel count, the fact that we discard the phase doesn't really make much difference. But the channel count can be changed in the configuration file. When I was testing the program, I had tried running it with 64 channels. It was quite noisy, because the program does not filter the output of Fourier transform, and doesn't set phase. It was also glitchy, caused by my computer, which was unable to process everything fast enough. But it was still better than 8 channels.
  10. A byte is an octet of bits, which can store 2⁸ = 256 possible values. To represent a character as a number, we need an encoding system. ASCII is one of them, and it assigns first 128 values to control characters, digits, English letters and punctuation. But people also use different languages, which need additional characters to be represented. Latin-1, for example, uses the remaining 128 values to define a few more characters from Latin-based scripts. Even with that, the character coverage was low: Chinese has several thousand different characters, for instance. Therefore, that was a need for an encoding system that allows to represent, ideally, characters from all scripts in use. Unicode is such a standard, and its latest revision has more than 130 thousand characters mapped to codepoints. Unicode has several possible encodings, e.g. UTF-8, UTF-16, and UTF-32, also defined by the Unicode standard. UTF-16 uses 16 bits (2 bytes) for each character, and UTF-32 uses 4 bytes. But they occupy too much data storage. UTF-8 has gained more use, at least on the web, and it uses 1 to 4 bytes to represent each character (so it usually uses less space). It's also the encoding used by OpenComputers. This is how UTF-8 encodes the string "¯\_(ツ)_/¯": Byte: c2 af 5c 5f 28 e3 83 84 29 5f 2f c2 af Chars: \___/ \/ \/ \/ \______/ \/ \/ \/ \___/ ¯ \ _ ( ツ ) _ / ¯ As you can see, some characters are encoded with a single byte (such as _, (, ), /, \); some need 2 bytes (¯); the tsu character in the middle (ツ) needs 3 bytes. When you open a file, there's a question of what it should work with. There are two possible answers. Non-binary. The stream works with UTF-8-encoded characters. Assuming the stream is at the beginning of the string "¯\_(ツ)_/¯", file:read(2) will return "¯\" — that's 3 bytes (#"¯\" == 3). This is the default mode. Binary. The stream works with single bytes. With the same assumption, file:read(2) will return "¯", a single character. If you need to use it, you have to explicitly set it by adding the character "b" to the mode string (io.open(path, "rb"), for instance). In binary mode, CC is unable to store non-UTF-8-encoded data in its strings (in other words, it does support strings encoded with UTF-8, but not raw bytes), so it has to return a number instead. This is not how PUC-Rio's implementation of Lua, or OC's natives (which are based on the former) work — they return a string with a single byte instead. But you can get the byte number by using the function string.byte, and string.char to do the opposite. local str = "\xc2" local byte = string.byte(str) print(byte == 0xc2) --> true print(string.char(byte) == str) --> true
  11. This behaviour is strange and indicates there's a bug somewhere. Since the double buffering library is used by MineOS, it would have much more chance of being noticed, so it's more likely there's some problem in your code. Could you show the code you run, please?
  12. Yeah, basically. The buffer is a singleton. In other words, there's only a single buffer instance in the memory, which is kept there even if programs that required the library exited. An implication of this fact is that buffer may be non-empty when you start your program; therefore, before you use the buffer, you need to do a buffer.clear() followed by a forceful render: buffer.drawChanges(true). It's been a long time since I've used that library, though. Things might have changed. Considering there aren't proper releases and commit descriptions, it's rather difficult to navigate through the commit history.
  13. OC sets significant limits the GPU, which make it much harder to do more advanced graphics. You can optimize the calls at the cost of high RAM usage — storing data in a buffer and only drawing the cells that changed since the last buffer flush. It isn't really an easy thing to implement yourself, so you might want to use a buffering library someone else made — here's one that definitely works, for example. I was also making one, but it's not yet ready. OpenComputers has a font.hex file, which is the font that's used to render characters on the screen. Since OC 1.6, asie's funscii font is used. Let's make things clear. Unicode is a standard that assigns numbers (codepoints) to a ton of characters (glyphs from different scripts, math symbols, emoji, and some other random things). UTF-8 is one of the possible encodings for Unicode symbols, and it's fairly the most popular one used. It converts a character codepoint, like U+2588, to a sequence of bytes. We need an encoding because a byte can only contain 256 values, while Unicode has 137439 characters. OpenComputers (and a lot of other software) supports UTF-8 and uses it to encode characters. OpenComputers provides the Unicode API for Lua 5.2 and Lua 5.3 architectures. You can use it to convert codepoints to characters and perform other operations on UTF-8 strings. Lua 5.3 also exports the utf8 library. It has a few functions that Unicode API doesn't provide, but is unavailable for Lua 5.2.
  14. Yeah, there is a way to make it faster. In fact, way faster. One of the things that makes it slow is... gpu.setBackground. It's quite expensive in terms of perfomance (tier 3 GPUs consume 1/128 of the call budget when this method is called). Fortunately, your program only involves 4 colors, so it can be easily optimized. Actually, there are two ways to do it. A somewhat naive way would be to process all cells of one color first, then process all cells of another color, etc. That would solve the issue with gpu.setBackground, but there's another thing to worry about. The gpu.set method isn't free. In fact, that program does 160 × 50 = 8000 sets, assuming it's running on the max tier 3 resolution. And that is a lot. Fortunately, OC supports Unicode. In particular, there's a character in Unicode called a full block — █. On OC, I prefer to think of it as of an inverse of " " (a space). If the foreground and background colors are swapped, the space would look like the full block, and vice versa. And it can help us reduce amount of GPU calls significantly. Try replacing the loops with the following: -- i = 0 gpu.setBackground(0xff0000) gpu.set(1, 1, " ") -- i = 1 gpu.setBackground(0x00ff00) gpu.set(1, 2, " ") -- i = 2, 3 gpu.setForeground(0xffff00) gpu.setBackground(0x0000ff) local fullBlock = "\u{2588}" local oddRow = (" " .. fullBlock):rep(math.floor(mx / 2)) local evenRow = (fullBlock .. " "):rep(math.floor(mx / 2)) for y = 1, my do local x = 1 if y % 2 == 1 then if y == 1 then -- Shift the x by 2 to the right to avoid rewriting -- the first two characters. -- OC truncates everything that goes out of the screen bounds, -- so there's no need to worry about that. x = x + 2 end gpu.set(x, y, oddRow) else gpu.set(x, y, evenRow) end end I can't launch Minecraft to run the program and measure the render time, but I can calculate the amount of call budget consumed by the GPU. Assuming the program runs on a tier 3 GPU and screen, there are 4 gpu.setForeground/gpu.setBackground calls, and 52 gpu.set calls. We get (4 × 1 / 128) + (52 × 1 / 256) = 15 / 64 ≈ 0.23. This is enough even if you have a tier 1 processor (whose call budget is 0.5), and the program should be able to run at 20 FPS, though it doesn't really change each frame. Oh, I meant to say "T3" (tier 3), sorry. A tier 2 GPU can only perform 128 sets and 64 fills. https://github.com/MightyPirates/OpenComputers/blob/master-MC1.7.10/src/main/scala/li/cil/oc/server/component/GraphicsCard.scala#L55-L60 Also, there's a comment a few lines above in that file, which reminds me of yet another reason OC makes indirect calls take 1 tick to run — synchronization. OpenComputers, very much unlike ComputerCraft, saves the state of running computers so that they can be resumed when the chunk is reloaded. Methods that interact with the world in some way (like moving items around) must perform synchronization with the Minecraft world to avoid, well, messing things up. The comment says it's especially important on world saves. Despite that, the GPU methods were made direct, although a "nasty trick" was needed to do this. Well, safe concurrency is hard to achieve.
  15. Please calm down. There are some component methods that block the computer for a tick when called, yes. Such methods usually interact with the world. For example, a transposer allows to transfer items between invetories by calling the transferItem method, which takes a tick to execute (so you get up to 20 stacks/second). On the other hand, there are a lot of direct methods, which can be called several times per tick. For example, the GPU's methods are direct. And the processor (as well as RAM) determines how many direct calls are allowed per tick. I think T2 T3 setup allows you to call gpu.get one thousand times every tick. Or 256 gpu.sets. Or 128 gpu.fills. I also believe you don't realize how bad ComputerCraft is for servers. A really simple and stupidly-written program can eat up all the memory the server has. That's why OC limits the memory available for an in-game computer. And running CPU-intensive tasks on a CC's computer makes the whole server run slow, whereas OC requires scripts to pause to allow other in-game computer to run. OpenComputers imposes the limits not because the mod developers want users to suffer (although I do sometimes think of that when I try to do some stupidly complex things with it), but to make it server-friendly. What's important is that OC allows you to configure these limits for your needs. Have you opened its configuration file? It is more than a 1.5k lines long, and has more than 300 settings you can change, each with a comment that explains the effect of the setting. There's callBudgets to increase the direct call number limit. Or decrease it. There's also the ramSizes setting for memory, and hddSizes for the HDD sizes. But even with all of these limits, OC can run a lot of programs, including graphical shells, 3D games, reactor control systems. It allows you to send HTTP requests to the real websites, and open raw TCP sockets to interact with network services. It can display 320x200 images. It can even play videos — inside the game, and on the default OC settings!
  • Create New...

Important Information

By using this site, you agree to our Terms of Use and Privacy Policy.