cc-pkg: A ComputerCraft Package Manager

ComputerCraft is a Minecraft mod that adds Lua-based computers. Over time, many programs have been created, and several package managers have come and gone. As I write this, all that I have seen are gone – their original authors have moved on, and shut down the servers hosting packages.

Now it’s my turn to sell you a package manager: cc-pkg. Unlike the others, I expect this to remain viable – even if I’m gone from the picture. If you want to skip to trying it, here’s how it’s installed (and how to ask for help):

pastebin get 9Li3u4Rc /bin/pkg
/bin/pkg help

(I’d also recommend installing the unix-like package, which adds /bin to your path, among a few other small tweaks.)

Why is cc-pkg different?

  1. It is built on ComputerCraft’s pastebin integration.
  2. It does not require a maintainer.
  3. It is extremely simple – and flexible.

cc-pkg has the same three sub-commands of the default pastebin program: get, run, and put*. They each do exactly what you’d expect them to do, except they can use package names as well as pastebin IDs. Package names are alphanumeric characters and dashes, and there are two types of packages.

Both are plaintext lists of the form key=value. The main type of package is a list of file paths (all starting with a forward slash) as keys set to package names or pastebin IDs as values. This is how cc-pkg knows which files to download and where to put them. The second type is simply called a list; and contains package names as keys, set to package names or pastebin IDs. Lists are saved to a local file cc-pkg uses to resolve package names – overwriting any existing entries with the same package name, which is how updating is done.

With just those core features, I think the system is viable. But since writing the first draft of this, I added one more feature to make cc-pkg more extensible: command extensions.

Under the Hood

cc-pkg keeps metadata through the following files:

  • /etc/pkg/names.list: The master list of package names cc-pkg knows.
  • /etc/pkg/ids.list: A raw ordered list of every pastebin ID cc-pkg has successfully downloaded.
  • /etc/pkg/<package-name>: The file describing each installed package is itself stored by name.

It uses global functions so that it can be loaded as an API to make a more advanced package management system on top of it – or just to make programs automatically download requirements using cc-pkg.

  • get(name_or_id, path): The core function that installs a package (path is optional).
  • down(id): Downloads from a pastebin ID.
  • save(path, data): Writes data to a file.
  • append(path, data): Appends data to a file.
  • id(name_or_id): Recursively checks the package names list until a pastebin ID is returned.
  • type(data): Recognizes data as a package, list, or unknown type.
  • src(data): Combines this data with existing package names (overwriting if duplicates exist).

On top of this, if a file is saved to /lib/pkg-commands/<name> and a user runs pkg <name>, that file will be run with the other arguments. This allows adding new functions, and overwriting the core functions to add additional features, if desired.

An example of this is the pkg-search package, which adds a search command to look for specific package names within the master list. I am also considering adding an extension which downloads short descriptions of packages, allowing you to view what is available.

*The put command is not implemented as of version 1.4.2 1.5.2, the latest at the time of writing finishing this introduction.

Switches Suck

I don’t like switch statements, in any language. They seem unnecessarily verbose and error-prone, in fact I forgot the break statements in my example below on the first draft. Most of the time, you don’t want the fall-through feature, but you have to remember that extra word for each case to prevent that.

switch (n)
{
    case 1:
    // something useful
    break;
    case 2:
    // another useful option
    break;
    default:
    // nothing matched
}

I also really hate the indenting used in most examples (including my own), as it makes it more difficult to visually parse. I prefer to just create an object with keys based on the possible values, and access what you need directly.

-- we're gonna pretend these are useful functions dependent on a star's type..something to do with heat?
local default = {}           -- used for a unique key
local heat = {
  A = function() end,        -- pretend these are full of useful code
  B = function() end,
  G = function() end,        -- and so on
  [default] = function() end -- default which can't be accidentally chosen
}

-- make sure we don't error, and call default if needed
if heat[star.type] then
  heat[star.type]()
else
  heat[default]()
end

Better Fluid Storage

A while back, I posted a prototype fluid storage system with a mechanic for handling breaches in a pressurized system. I thought I’d be clever by storing fluids as a percentage of a defined volume and pressure. For a “simplified” system, it was quite complicated, and fundamentally flawed.

This time, it is straightforward. Keep track of the amounts of each fluid, a total sum, and volume of the container. Pressure is the sum divided by the volume, and the percent of a fluid is its amount divided by the total sum of all fluids.

tank = {
  volume: 200, sum: 300,
  contents: { hydrogen: 200, oxygen: 100 }
}
pressure = tank.sum / tank.volume -- 1.5
percent_hydrogen = tank.contents.hydrogen / tank.sum -- 0.67

Everything needed from a container can be accessed immediately or with a single calculation involving only two variables.

But what about hull breaches?

Fluids vs Mechanical Classes

I realized that I should define fluid containers very narrowly, all they need care about is a small set of numbers, and have a few functions to modify that state. Enter the Breach class.

Breach(fluidA, fluidB, volume)

Specify which fluid containers are interacting and the volume ( I guess technically it should be area) of the breach. Each update cycle moves the pressure difference multiplied by the volume (area) of the breach from the higher pressure container to the lower pressure container.

What about pumps? I have those, with a “volume” and a “rate” modifier to allow you to adjust how fast the pump works. Pumps only work in one direction, but have a function to reverse them.

Want only one fluid to go through..say, a filter? Made that as well. Valves, so that you can adjust flow rate, filter-pump combos for pushing just the right amount of one fluid, and one-way valves to allow pressure to escape but not allow any blowback.

The Flaws

  • Once pressure is equalized, contents do not mix between fluids.
  • All fluids have the same density. This probably isn’t that hard to fix, but is unneeded for my purposes.
  • All fluids mix. This may or may not be harder to fix depending on how it is approached.
  • Temperature isn’t simulated at all. I would love to have heat transfer and heat affecting density, but these details are not necessary for my current project.

The Code

As of publishing this article, I don’t have a library to give you, but I will update it as soon as I do release it. For now, here is where I have the beginnings of a library. No matter what, I can promise it will be available by the end of April (or upon request).

Simplified Fluid Storage System

One of my game ideas involves constructing 2D spaceships, and the concept of a simplified system for storing fuel, oxygen, water – really any kind of fluid mixture – in storage tanks. Along with this, it allows simulating breaches between containers, hard vacuum, and the pressurized areas of the ship itself!

{ -- a rough approximation of Earth's atmosphere
  pressure: 1
  volume: 4.2e12 -- 4.2 billion km^3
  contents: {
    nitrogen: 0.775
    oxygen: 0.21
    argon: 0.01
    co2: 0.005
  }
}

{ -- hard vacuum
  pressure: 0
  volume: math.huge -- infinity
  -- the contents table will end up containing negative infinity of anything that leaks into the vacuum
}

It all comes down to storing a total pressure and volume per container, and a table of contents as percentages of the total mixture. The total amount of mass in the system can be easily calculated (volume * pressure), as can the amount of any item in the system ( volume * pressure * percent).

Breaches are stored as a reference in the container with a higher pressure, and a size value is added to the container with lower pressure (representing the size of the hole between them).

Limitations

  • Everything has the same density and mixes evenly.
  • There are no states of matter, everything is treated as a gas.
  • Attempting to directly modify the amount of a fluid is prone to floating-point errors it seems, while mixing containers via the breach mechanic is working as expected.

The code was written in MoonScript / Lua and is available here.

My First Ludum Dare (#32)

Grand Theft Papercut

This was the 4th Ludum Dare that I wanted to participate in, and the first where I actually did. I was inspired by my recent discovery of a GameBoy GTA game, and my unconventional weapon (theme) was a deck of cards.

It was also my first time making anything tile-based, and it worked very well for my experience at the time. Even better, it was the first time I had the capability to load and save, and the game was also the map editor for the game. Very much something I’d like to do again sometime.

You can go play the prototype here.