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:
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!)
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
:
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
:
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.