How to use mods with Draftsman

How does Draftsman use mods?

One of the motivations for creating Draftsman was creating a tool for blueprint strings that would be robust to user error, since Factorio itself lacks in this department. Factorio gives errors (sometimes), though making them readable was never very high on Wube’s list, since 99 percent of the time blueprint strings are modified and generated internally. Additionally, there are some operations you can perform on blueprint strings that import fine, but are incorrect from a gameplay standpoint that a user might want to be notified if they set accidentally. Draftsman was designed with the intention of making these user errors unlikely and easy to fix, but doing so requires a lot of information on the the actual mechanics of the game itself. Information on entities, tiles, instruments, items, signals, and more all need to be known in order to give the user meaningful validation, so Draftsman can know when they go “out-of-bounds”.

In order to collect this data in an accurate and robust way, Draftsman tries to emulate the data portion of the Factorio data lifecycle as accurately as possible, and then extracts the needed information from the loaded Lua tables. This makes the data perpetually up to date with every Factorio release, and meaning there is only one location where Factorio’s data is actually specified (Wube themselves). This allows us to now know that a “filter-inserter” is a FilterInserter, that it is rotatable, circuit connectable, and that it has 5 unique filter slots, all of which we dynamically determined from this load process.

Of course, because we’re emulating the game’s loading process directly, there’s nothing stopping us from including mods into this process as well! Doing so allows us to get the same level of validation that we get on vanilla entities as with modded ones; we can tell if a AAI warehouse’s inventory bar exceeds it’s inventory size, or if the wire connection distance between a Space Exploration pylon is too great for Factorio to connect, or that the entity "ltn-train-sotp" does not exist (and should be "ltn-train-stop" instead).

This process is usually done with the script entry-point named draftsman-update, which can be called from the command line when you install Draftsman:

(.venv) $ draftsman-update --help
usage: draftsman-update [-h] [-v] [-p PATH] [-l] [--no-mods]

options:
-h, --help            show this help message and exit
-v, --verbose         Show extra information during the update
-p PATH, --path PATH  The path to search for mods; defaults to `python_install/site-packages/draftsman/factorio-mods`
-l, --log             Display any 'log()' messages to stdout; any logged messages will be ignored if this argument is not set
--no-mods             Only load the 'base' mod and ignore all others; simulates no mods

When you run draftsman-update, the Factorio settings and data stage is run, and then the data is extracted to a set of pickle files, located in the draftsman/data folder in the installation directory. This data is cached, which means that you only need to run draftsman-update once every time you change the mod list you’re working with.

draftsman-update can also be called in script via the method draftsman.env:update() if you want to change the mod list on the fly:

# my_update_script.py
from draftsman.env import update
update(verbose=True, path="some/path") # equivalent to 'draftsman-update -v -p some/path'

Installing mods

Adding mods to Draftsman is about as easy as installing mods for Factorio itself. First, we need to determine exactly which mods we want to work with. Mods can be downloaded directly from https://mods.factorio.com/, but an easier method is to use Factorio’s in-game mod browser itself:

Factorio's in-game mod browser.

Select the mods that you want, then press the Install button to automatically download and install the mods and their dependencies. You can also setup any user configuration that you want, such as enabling or disabling specific mods, or changing mod specific settings; these changes will be reflected in Draftsman. (As long as they affect the data stage only!)

Factorio's mod settings, available in Settings > Mod Settings

Then, navigate to the Factorio mods folder. This is usually located somewhere in your home directory. For example, on Windows your mods should be located in C:\Users\your_name\AppData\Roaming\Factorio\mods:

The local mods folder for Factorio.

There are two ways of letting Draftsman know where to look for mods. One way is to copy the mods you want to the local draftsman/factorio-mods folder located in the Draftsman installation location. This method is convenient if you want to develop scripts for a particular version of a mod or mods, your actual Factorio modlist changes frequently, or just want to isolate the Draftsman environment from your base Factorio one.

If you installed Draftsman in a virtual environment it should be located in a Lib/site-packages folder somewhere in your development directory. If you installed it to your computer’s Python installation, check to see where that’s installed (depending on your OS) and look for the same site-packages folder. You should make sure that the version of Draftsman that you’re using corresponds the the installation location you put the mods, if you have multiple instances of Draftsman installed.

In that folder you should find a folder titled draftsman, and inside that a folder named factorio-mods:

A depiction of the draftsman mods folder.

Copy and paste the contents from your Factorio mods folder to the factorio-mods folder in draftsman. You should include the mod-settings.dat and mod-list.json files, as they hold the mod configuration settings you specified in Factorio. Any other file that does not end with .zip will be ignored.

However, it might be more convenient to just associate your Factorio mod folder with Draftsman, where you want to keep a close link between the scripts you’re writing and the Factorio mods you’re playing. In this case, instead of copying the mods you can specify a -p or --path argument to draftsman-update which changes the search location for mods to that path.

Then, to load these changes into Draftsman simply run draftsman-update if you copied the mods, or draftsman-update -p some/path/to/your/mods if the mods are located somewhere else. You can use the --verbose or -v flag to get more information about the load process; the following is something like the verbose output of the modlist above:

(.venv) $ draftsman-update --verbose
aai-containers 0.2.10
dependencies:
        base >= 1.1.0
aai-industry 0.5.14
dependencies:
        base >= 1.1.0
        ? aai-containers >= 0.1.1
        ? InserterFuelLeech >= 0.2.6
        ? angelsrefining
        ? IndustrialRevolution >= 2.2.3
aai-signal-transmission 0.4.4
dependencies:
        base >= 1.1.0
Aircraft 1.8.4
dependencies:
        base >= 1.1.0
        ? bobplates >= 1.1.0
        ? bobelectronics >= 1.1.0
        ? boblibrary >= 1.1.0
        ? bobwarfare >= 1.1.0
        ? bobvehicleequipment >= 1.1.0
        ! traintunnels <= 0.0.11
alien-biomes 0.6.7
dependencies:
        base >= 1.1.0
        ? alien-biomes-hr-terrain >= 0.3.1
ArmouredBiters 1.1.5
dependencies:
        base >= 1.1.1
        ? alien-biomes
cargo-ships-graphics 0.1.0
dependencies:
        base >= 1.1
        ~ cargo-ships >= 0.1.0
cargo-ships 0.1.16
dependencies:
        base >= 1.1
        cargo-ships-graphics >= 0.1.0
        ? factorio-world >= 1.0.2
        ? NewIslands >= 0.1.0
        ? islands_world >= 1.1.0
        ? SeaBlock >= 0.5.5
        ? Hovercraft >= 0.0.1
        ? Hovercrafts >= 1.1.0
        ? angelspetrochem >= 0.9.17
        ? ctg >= 0.4.3
        ? Krastorio2 >= 1.0.18
        ! cargo-ships-seraph
        ? rso-mod
CleanedConcrete 1.0.2
dependencies:
        base >= 0.18.0
flib 0.10.1
dependencies:
        ? base >= 1.1.35
helmod 0.12.9
dependencies:
        base >= 1.1
informatron 0.2.2
dependencies:
        base >= 1.1.0
islands_world 1.1.0
dependencies:
        base >= 0.15
jetpack 0.3.1
dependencies:
        base >= 1.1.0
        ? PickerTweaks
LogisticTrainNetwork 1.16.7
dependencies:
        base >= 1.1.46
        flib >= 0.6.0
        ? cargo-ships
Noxys_Swimming 0.4.2
dependencies:
        base >= 1.1.0
Noxys_Waterfill 0.4.3
dependencies:
        base >= 1.1.50
recursive-blueprints 1.2.6
dependencies:
        base
robot_attrition 0.5.12
dependencies:
        base >= 1.1.0
shield-projector 0.1.3
dependencies:
        base >= 1.1.0
space-exploration-graphics-2 0.1.2
dependencies:
        base >= 1.1.0
space-exploration-graphics-3 0.1.1
dependencies:
        base >= 1.1.0
space-exploration-graphics-4 0.1.1
dependencies:
        base >= 1.1.0
space-exploration-graphics-5 0.1.2
dependencies:
        base >= 1.1.0
space-exploration-graphics 0.5.15
dependencies:
        base >= 1.1.0
space-exploration-postprocess 0.5.29
dependencies:
        base >= 1.1.0
        ? space-exploration >= 0.5.104
        ? angelsbioprocessing
        ? angelsindustries
        ? angelspetrochem
        ? angelsrefining
        ? angelssmelting
        ? bobassembly
        ? bobelectronics
        ? bobenemies
        ? bobgreenhouse
        ? bobinserters
        ? boblogistics
        ? bobmining
        ? bobmodules
        ? bobores
        ? bobplates
        ? bobpower
        ? bobrevamp
        ? bobtech
        ? bobvehicleequipment
        ? bobwarfare
        ? Darkstar_utilities
        ? Darkstar_utilities_Low_Spec-0_17-Port
        ? Decktorio
        ? k2se-compatibility
        ? Krastorio2 >= 1.2.22
        ? NPUtils
        ? qol_research
        ? reverse-factory >= 6.0.5
        ? SpaceMod
        ? Yuoki
space-exploration 0.5.112
dependencies:
        base >= 1.1.50
        aai-industry >= 0.5.3
        alien-biomes >= 0.6.4
        jetpack >= 0.2.6
        robot_attrition >= 0.5.9
        shield-projector >= 0.1.2
        space-exploration-graphics >= 0.5.15
        space-exploration-graphics-2 >= 0.1.2
        space-exploration-graphics-3 >= 0.1.1
        space-exploration-graphics-4 >= 0.1.1
        space-exploration-graphics-5 >= 0.1.2
        ~ space-exploration-postprocess >= 0.5.28
        informatron >= 0.2.1
        aai-signal-transmission >= 0.4.1
        ? aai-containers >= 0.2.7
        ? bullet-trails >= 0.6.1
        ? grappling-gun >= 0.3.1
        ? combat-mechanics-overhaul >= 0.6.15
        ? equipment-gantry >= 0.1.1
        ! angelsindustries
        ! angelspetrochem
        ! angelsrefining
        ! angelssmelting
        ! bobelectronics
        ! bobores
        ! bobplates
        ! bobpower
        ! bobrevamp
        ! bobtech
        ! bobvehicleequipment
        ! bobwarfare
        ! Yuoki
        ! pycoalprocessing
        ! pyindustry
        ! pyhightech
        ! ab_logisticscenter
        ! angelsinfiniteores
        ! BasicSeaBlock
        ! BitersBegone
        ! BitersBegoneUpdated
        ! bobmodules
        ! bulkteleport
        ! Clockwork
        ! dangOreus
        ! Darkstar_utilities
        ! dark-matter-replicators
        ! dark-matter-replicators-0_17-port
        ! DeepMine
        ! endlessresources
        ! ExplosiveExcavation
        ! FactorioExtended-Core
        ! FactorioExtended-Plus-Core
        ! IndustrialRevolution
        ! IndustrialRevolution2
        ! inf_res
        ! infinite-resources-depletion
        ! ItemTeleportation
        ! LandfillPainting
        ! Li-Quarry
        ! modmash
        ! MoreScience
        ! MoreSciencePacks
        ! omnimatter
        ! OnlyReds
        ! PersonalTeleporter
        ! pickerextended
        ! pickerinventorytools
        ! PlacePump
        ! PumpAnywhere
        ! PyBlock
        ! quarry
        ! quarry-edit
        ! railgun_revival
        ! rso-mod
        ! SeaBlock
        ! SchallMachineScaling
        ! SchallOreConversion
        ! sonaxaton-infinite-resources
        ! Space-Exploration-Modpack
        ! SpaceMod
        ! TagToTeleport
        ! TeamCoop
        ! Teleportation_Redux
        ! traintunnels
        ! Unlimited-Resources
        ! UnlimitedProductivity
        ! vtk-deep-core-mining
        ! warptorio
Load order:
['base', 'Aircraft', 'CleanedConcrete', 'Noxys_Swimming', 'Noxys_Waterfill', 'aai-containers',
'aai-signal-transmission', 'alien-biomes', 'cargo-ships-graphics', 'flib', 'helmod', 'informatron',
'islands_world', 'jetpack', 'recursive-blueprints', 'robot_attrition', 'shield-projector',
'space-exploration-graphics', 'space-exploration-graphics-2', 'space-exploration-graphics-3',
'space-exploration-graphics-4', 'space-exploration-graphics-5', 'ArmouredBiters', 'aai-industry',
'cargo-ships', 'LogisticTrainNetwork', 'space-exploration', 'space-exploration-postprocess']
SETTINGS.LUA:
        mod: Aircraft
        mod: Noxys_Swimming
        mod: Noxys_Waterfill
        mod: aai-containers
        mod: alien-biomes
        mod: flib
        mod: helmod
        mod: informatron
        mod: jetpack
        mod: recursive-blueprints
        mod: robot_attrition
        mod: ArmouredBiters
        mod: aai-industry
        mod: cargo-ships
        mod: LogisticTrainNetwork
        mod: space-exploration
SETTINGS-UPDATES.LUA:
        mod: cargo-ships
SETTINGS-FINAL-FIXES.LUA:
DATA.LUA:
        mod: base
        mod: Aircraft
        mod: CleanedConcrete
        mod: Noxys_Swimming
        mod: Noxys_Waterfill
        mod: aai-containers
        mod: aai-signal-transmission
        mod: alien-biomes
        mod: flib
        mod: helmod
        mod: informatron
        mod: islands_world
        mod: jetpack
        mod: recursive-blueprints
        mod: robot_attrition
        mod: shield-projector
        mod: ArmouredBiters
        mod: aai-industry
        mod: cargo-ships
        mod: LogisticTrainNetwork
        mod: space-exploration
        mod: space-exploration-postprocess
DATA-UPDATES.LUA:
        mod: base
        mod: Aircraft
        mod: Noxys_Waterfill
        mod: alien-biomes
        mod: islands_world
        mod: recursive-blueprints
        mod: robot_attrition
        mod: aai-industry
        mod: cargo-ships
        mod: space-exploration
DATA-FINAL-FIXES.LUA:
        mod: aai-containers
        mod: alien-biomes
        mod: jetpack
        mod: robot_attrition
        mod: aai-industry
        mod: cargo-ships
        mod: LogisticTrainNetwork
        mod: space-exploration
        mod: space-exploration-postprocess
Extracted mods...
Extracted entities...
Extracted instruments...
Extracted items...
Extracted modules...
Extracted recipes...
Extracted signals...
Extracted tiles...
Update finished.
hella slick; nothing broke!

Note

In Factorio, mods can be loaded as either a zip file or a folder. Currently, Draftsman can only properly load zip files, though it will be able to load mod folders as well in the future. A workaround for now is to simply wrap the mod folder in a zip file, according the the mod naming conventions.

Writing scripts with mods

After the command has finished, all the correct data and prototypes should be loaded. This means that you can create new instances of modded entities exactly as if they were vanilla ones:

from draftsman.entity import new_entity
from draftsman.data import entities

# For example, let create the "blueprint-deployer" entity from Recursive Blueprints
deployer = new_entity("blueprint-deployer")
print(deployer)
# <Container>{'name': 'blueprint-deployer', 'position': {'x': 0.5, 'y': 0.5}}
assert deployer.inventory_size == 1
assert deployer.inventory_bar_enabled == False
# deployer.bar = 10 # DraftsmanError: This entity does not have bar control

# Lets see what the new list of containers are, now that we include mods
print(entities.containers)
# ['steel-chest', 'iron-chest', 'wooden-chest', 'aai-strongbox', 'aai-storehouse',
# 'aai-warehouse', 'blueprint-deployer', 'se-rocket-launch-pad', 'se-rocket-landing-pad',
# 'se-delivery-cannon-chest', 'se-cargo-rocket-cargo-pod', 'aai-big-ship-wreck-1',
# 'big-ship-wreck-1', 'aai-big-ship-wreck-2', 'big-ship-wreck-2', 'aai-big-ship-wreck-3',
# 'big-ship-wreck-3', 'aai-medium-ship-wreck-1', 'aai-medium-ship-wreck-2', 'blue-chest',
# 'red-chest', 'se-cartouche-chest', 'factorio-logo-11tiles', 'factorio-logo-16tiles',
# 'factorio-logo-22tiles']

However, note that the converse is not necessarily true; If you have the mod enabled, then you can create a modded entity, but if you share that script with another user who doesn’t have those mods enabled, the script will fail with an InvalidEntityError.

To handle this case more elegantly, there exists a mods module that indexes the currently enabled mods and their versions (similar to Factorio’s modding API).

from draftsman.data import mods

print(mods.mod_list)
# {
#     'base': (1, 1, 57, 0), # This is the Factorio version, which is treated as a "mod"
#     'aai-containers': (0, 2, 10),
#     'aai-industry': (0, 5, 14),
#     'aai-signal-transmission': (0, 4, 4),
#     'Aircraft': (1, 8, 4),
#     'alien-biomes': (0, 6, 7),
#     'ArmouredBiters': (1, 1, 5),
#     'cargo-ships-graphics': (0, 1, 0),
#     'cargo-ships': (0, 1, 16),
#     'CleanedConcrete': (1, 0, 2),
#     'flib': (0, 10, 1),
#     'helmod': (0, 12, 9),
#     'informatron': (0, 2, 2),
#     'islands_world': (1, 1, 0),
#     'jetpack': (0, 3, 1),
#     'LogisticTrainNetwork': (1, 16, 7),
#     'Noxys_Swimming': (0, 4, 2),
#     'Noxys_Waterfill': (0, 4, 3),
#     'recursive-blueprints': (1, 2, 6),
#     'robot_attrition': (0, 5, 12),
#     'shield-projector': (0, 1, 3),
#     'space-exploration-graphics-2': (0, 1, 2),
#     'space-exploration-graphics-3': (0, 1, 1),
#     'space-exploration-graphics-4': (0, 1, 1),
#     'space-exploration-graphics-5': (0, 1, 2),
#     'space-exploration-graphics': (0, 5, 15),
#     'space-exploration-postprocess': (0, 5, 29),
#     'space-exploration': (0, 5, 112)
# }

You can use this to ensure that the mods needed for the script’s operation are present and of the correct version, and issue more helpful messages when they are not:

from draftsman.entity import Container
from draftsman.data import mods
from draftsman.error import MissingModError

if not mods.mod_list.get("recursive-blueprints", False):
    raise MissingModError("The Recursive Blueprints mod is needed for this script")

deployer = Container("blueprint-deployer")
# ...

Limitations of mods

Draftsman (currently) only implements the Settings and Data stage of the data lifecycle. This means that any mod functionality that lies outside of that stage is not considered or available to the programmer. This includes custom hooks for placing entities, removing entities, on game load, unload, and any other per world or instance operation.

A good example of this is the Cargo Rocket Launchpad from the Space Exploration modpack; This entity has a set of metadata associated with its tags attribute that is generated when it is placed in the world. This behavior has to be manually mimicked by a script writer in order to get the intended functionality of the entity; there is currently no mechanism to “query” what should happen to the entity when it’s placed in the world, or, harder yet, integrate these attributes as part of the structure of the class. It would be convenient to be able to specify a destination_location attribute for rocket launchpad, but at the moment this relies on the user to implement a custom Container entity with this behavior, and is not something to be expected to be automatically generated by Draftsman.

Potential Errors

Draftsman’s loading process was designed with how Factorio loads it’s data in mind, with the intention of being identical. However, this implementation is most likely incomplete across a number of edge cases, and I have only been able to test it’s functionality with a handful of mods. If you use Draftsman and come across an error that does not happen when loading the same mods with the same configuration in Factorio itself, please leave a issue so I can track and resolve it.