Skip to content
Nicholas Dunn edited this page Jun 4, 2019 · 12 revisions

Welcome to the Factorio Standard Library

What is the Factorio Standard Library

The Factorio Standard Library is a project to bring Factorio modders high-quality, commonly-required utilities and tools that developers have been often copying around, remixing, and rewriting poor quality copies of. Basic tools like logging, math calculations, and position or area manipulation, which involve repeated drudgery and are commonly reproduced in high-quality, documented form in the Factorio Standard Library.

How do I use the Factorio Standard Library in my mod

  1. Install the Factorio Standard Library mod from the mod portal latest release
  2. In your info.json add stdlib as a dependency.
  3. In your control.lua or other lua modules, you can import the stdlib modules you need.

For example, using the logger

For example, using the logger:

require 'stdlib/log/logger'

LOGGER = Logger.new('example-mod-name')

script.on_event(defines.events.on_tick, function(event)
    if event.tick % 600 == 0 then
        LOGGER.log("An example log message!")
    end
end)

In factorio, mod log output will be in %appdata%\factorio\script-output\logs (on windows), or ~/.factorio/script-output/logs (on OSX or Linux).

Avoiding iteration, and general stdlib tools

Often in factorio modding, you have an array of entities and need to apply some action or data to them. For example, if you want to damage or kill biters nearby players when they log in, you might write this code:

script.on_event(defines.events.on_player_created, function(event)
    local player = game.players[event.player_index].character
    local position = player.position
    local force = player.force
    local entities = player.surface.find_entities_filtered(area = {{position.x - 25, position.y - 25}, {position.x + 25, position.y + 25}, type = 'unit', force = 'enemy'})
    for _, entity in pairs(entities) do
        if entity.valid then
            entity.damage(100, force)
        end
    end
end)

That code is a bit verbose. Particularly creating the area around the player and damaging the entities is very long. It's easy to miss an - or + and make a mistake in the math, especially on long lines.

Improved code

Using the standard library, we can simplify this example. I'm going to use the position and table modules only, and we can shorten the code a bit.

require 'stdlib/table'
require 'stdlib/area/position'

script.on_event(defines.events.on_player_created, function(event)
    local player = game.players[event.player_index].character
    local force = player.force
    local valid_enemies = table.filter(player.surface.find_entities_filtered(area = Position.expand_to_area(player.position, 25), type = 'unit', force = 'enemy'}), function(e) return e.valid end)
    table.each(valid_enemies, function(entity)
         entity.damage(100, force)
    end)
end)

The second example has a few new require statements at the top, to give access to the extra table methods (table.filter and table.each), and requires the Position class to give access to Position.expand_to_area along with all other position methods.

So, what is the second code doing? Position.expand_to_area creates an area (square) with the player position at the center, and the edges of the area 25 blocks away from each side, just like the previous code did. table.filter takes an array, and is filtering out any that are not true (in this case, are not valid). table.each applies the function to each of the valid enemies, and damages them by 100 HP.

It is easier to understand and read what the code is doing in the second example, and much less error prone. There is no math to get wrong, no verbose iteration.

Improved improved code

However, we aren't done yet. There are a few final improvements that can be done. The function we wrote for table.filter is so common, it is also provided in the stdlib, in Game.VALID_FILTER. We can use that instead of rewriting it ourselves.

Also, we can use the Factorio stdlib event handlers. It allows mods to register multiple handlers for a single event (encouraging smaller, easier-to-read handlers) and has built in error handling. Errors in events will trigger a message in game instead of a hard-exit-to-menu.

Here's the final code!

require 'stdlib/table'
require 'stdlib/game'
require 'stdlib/event/event'
require 'stdlib/area/position'

Event.register(defines.events.on_player_created, function(event)
    local player = game.players[event.player_index].character
    local force = player.force
    local valid_enemies = table.filter(player.surface.find_entities_filtered(area = Position.expand_to_area(player.position, 25), type = 'unit', force = 'enemy'}), Game.VALID_FILTER)
    table.each(valid_enemies, function(entity)
         entity.damage(100, force)
    end)
end)

Entity Data

One of the other features the stdlib provides is an easy way to get and set mod data on entities. It is possible to do this yourself, using the global object to store persistent data, but often very cumbersome.

For example, if we wanted a simple mod that kept track of how far a player has driven any particular car, and printed this to the player after they step out (a nicer mod might provide a gui display, but that is a bit outside of the scope), the code would end up quite complex for this basic task:

script.on_event(defines.events.on_player_driving_changed_state, function(event)
    local player = game.players[event.player_index]
    if player.driving then
        local vehicle = player.vehicle
        -- create and keep track of every car
        -- global.cars is an array of tables, ex: { vehicle , start_pos, player_idx }
        if not global.cars then global.cars = {} end

        local data = { vehicle = vehicle, start_pos = player.character.position, player_idx = event.player_index }
        table.insert(global.cars, data)
    else
        if not global.cars then return end

        local found = nil
        for i, car in pairs(global.cars) do
            if car.player_idx == event.player_index then
                 found = car
                 table.remove(global.cars, i)
                 break
            end
        end
        if found then
            local end_pos = player.character.position
            local dist = math.sqrt((end_pos.x - found.start_pos.x) * (end_pos.x - found.start_pos.x) + (end_pos.y - found.start_pos.y) * (end_pos.y - found.start_pos.y))
            player.print("Traveled " .. dist .. " blocks from your starting position!")
        end
    end
end)

There is a lot of complex logic there. Global state has to be tracked, added to when a player enters a vehicle, and removed from when a player exits. The distance traveled from the starting position has to be calculated. Missing variables have to be assigned defaults and edge-cases where a player is in a vehicle before the mod loads have to be handled.

This can be much simpler.

Stdlib version

require 'stdlib/event/event'
require 'stdlib/entity/entity'
require 'stdlib/area/position'

Event.on_event(defines.events.on_player_driving_changed_state, function(event)
    local player = game.players[event.player_index]
    if player.driving then
        -- track the driving information on the player entity
        Entity.set_data(player.character, { vehicle = vehicle, start_pos = player.character.position, player_idx = event.player_index })
    else
        local data = Entity.get_data(player.character)
        if data then
            player.print("Traveled " .. Position.distance(data.start_pos, player.character.position) .. " blocks from your starting position!")
            -- clear driving info
            Entity.set_data(player.character, nil)
        end
    end
end)

The Factorio Standard Library simplifies much of the code here. The ability to set and retrieve data for an entity removes the need for the for loops, and managing global state ourselves. The ability to easily manipulate positions (and therefore calculate the distance) removes all the mathematical hazards. The mod is now about what actions what needs to happen, and not about complex game state manipulation.

Clone this wiki locally