Many hours have been wasted and we should avoid this. So, I would like to list the things that are not documented, or well hidden in the documents, so we don't make the same mistakes again.
Starting from the basic ones in data phase to the things about control phase:
Data
Prototype namingIt is important to make sure you have named your prototypes correctly. Impropriate prototype names may lead to mod incompatibility, errors will be thrown when your mod is loaded with another one.
Golden rule: add prefix to your prototype names, unless you know what you are doing.
For each prototype in each type (entity, item, fluid, recipe, technology, resource, etc.), its name has to be unique. You have to make sure it won't collide with any prototype of the same type provided by other mods.
When more mods are involved, being unique is more difficult. So, the easiest way is to add prefix, which is your mod ID.
For example, the ID of my Creative Mode is creative-mode, so you can see that every prototypes in the mod is named after "creative-mode_".
You may ask what "you know what you are doing" means. Well, think of some common intermediate products (type = "item" or "fluid") or resources (type = "resource"), like tin ores, silver ores, iron-wire, etc. items that may be commonly used in different mods and should be interchangeable. In these cases, adding prefix to them will make things more complicated. We don't want "mod1-tin_ore", "mod2-tin_ore", "mod3-tin_ore", "mod4-tin_ore"...... right? But if you go for the non-prefixed name, make sure the prototype of the same name has not been defined yet before you define it your own way. Error will be thrown if any prototype is defined more than once. To avoid that, you can either
1) add dependency to the mod that has the prototype defined, or
2) use if-then like the following (take tin-ore as an example):Note: pretty sure that items for building the entities in your mod need prefixed names.Code: Select all
if data.raw["resource"]["tin-ore"] then -- TODO: tin-ore has not be defined. Define it now. end
This rule can also apply to GUI naming in control phase. See GUI Naming for the reason.Error - invalid prototype array nilIf you encounter the error "invalid prototype array nil" and you are sure your prototype data is not nil, make sure you have writteninstead ofCode: Select all
data:extend{ ... }
( ":" vs "." )Code: Select all
data.extend{ ... }
It is fine to use "." if you use it this way:Code: Select all
data.extend(data, { ... })
Only some entity types support apply_runtime_tintNot everything supports masks with apply_runtime_tint. So far, I have only found the following entity types support this:No, unit does not support runtime masks.
- turret, ammo-turret, fluid-turret, electric-turret (force color)
- combat-robot (force color)
- gate (force color)
- locomotive (entity color)
- cargo-wagon (entity color)
- fluid-wagon (entity color)
- car (player color)
- simple-entity-with-owner (player color, force color (when LuaEntity.color is {r=0,g=0,b=0,a=0} or custom LuaEntity.color) (added in 0.15.33)
- simple-entity-with-force (force color) (added in 0.15.33)
Note: force color is the color of the first player in the force.Solution to incorrect icon sizeThere is a known inconsistency between the base mod and our mods in 0.15: the default icon size for item-group and technology is different.
While the default icon size for item groups from the base mod is 64x64 px, it is 32x32 px in our mods.
While the default icon size for technologies from the base mod is 128x128 px, it is also 32x32 px in our mods.
To use 128x128px icon for your own technologies, you will have to add the following in the technology data:Source: viewtopic.php?t=46437Code: Select all
icon_size = 128
Max image sizeWe know you can create beautiful and detailed graphics for Factorio, but make sure they are not too detailed (size is too big).
For normal resolution (graphics quality <= "normal"), the maximum supported size for each image is 2048px on any side.
For high resolution (graphics quality = "high"), the maximum supported size for each image is 4096px on any side.
If any of your images exceeds the above limits, you will see error like this:
- MaxSizeForHR.png (43.79 KiB) Viewed 13717 times
Icon tinting, Multi-layered iconIt is possible to tint icons and/or use multiple images in a single icon by changingintoCode: Select all
icon = "__foo__/graphics/my-icon.png"
Note: so you shouldn't expect the "icon" attribute to always exist. It can be presented in "icons" instead.Code: Select all
icons = { { icon = "__foo__/graphics/my-icon-1.png", tint = {r = 0.8, g = 0.8, b = 1} }, { icon = "__foo__/grahpics/my-icon-2.png", tint = {r = 1, g = 0.5, b = 0.5} } }
How to use data.rawOnce the data has been registered by data:extend, you can access the data byand then you will get the original table.Code: Select all
data.raw[type][name]
For example, you can change the max_health of the vanilla fluid wagon like this:while the first "fluid-wagon" is the type and the second one is the name.Code: Select all
data.raw["fluid-wagon"]["fluid-wagon"].max_health = 100000
You may also want to see "Data structure in data vs control"fast_replaceable_group is not the magic solutionEven though it is possible to put everything into the same fast replaceable group by setting "fast_replaceable_group", but Factorio only supports replacing entities of the same size, i.e. not possible to replace a transport belt with splitter because transport belt is 1x1 while splitter is 1x2.order is not black magicHave you ever wondered what is the magic behind the ordering in Factorio? Like, is "a-b-c" a special format?
Well, the answer is NO. "a-b-c" is not a special format. It is just a string. It can be "whateveryoulike". And the logic behind the ordering function is simply^ Provided by Rseding91Code: Select all
if left.order < right.order then left go first end
Also, from the official wiki about order: https://wiki.factorio.com/Types/OrderThe order property is a simple string.player-port is specialSomethings special about player-port:
- Its animation speed is unchangeable.
- All player ports play the same frame at the same time.
- If a player port is placed in game, it will immediately respawn player when the player is killed.
Large maximum_wire_distance can cause lagFrom time to time, you might see mods that increase the wire distance of electric poles by changing their values of "maximum_wire_distance" to sky high. But actually, that may make the game lags when you build the poles manually. This has been classified as "Not a bug": viewtopic.php?f=23&t=28095stream projectile particle_buffer_size has to be at least 2You can't set "particle_buffer_size" of "stream" projectiles to be smaller than 2, or otherwise it won't work as expected (or error will simply be thrown).
According to posila's response to stream prototype particle_buffer_size=1 causes display bug (the thread was renamed as the original report turned out to be not a bug, but it revealed another bug):posila wrote:It is supposed to create illusion of continuous stream of fluid by drawing line segments to form parabolic shape. The line segments are drawn between particles generated in regular interval and while the turret is shooting segment between last particle and muzzle of the turret is also drawn. When the turret stops shooting one last particle is generated as "tail" so the stream is not detached from the turret rapidly.
To keep track of the particles it uses circular buffer of size particle_buffer_size, so in your case "tail" particle overwrites head. With single particle there is no segment to draw.cluster_count has to be at least 2You can't set "cluster_count" to be 1 in "cluster" actions. API request for making it possible has been made: Allow cluster_count to be 1push-back effect takes the target entity collider size into accountBefore working on making a "push-back" effect, you may want to know how it works first. To avoid being merged into another entity, collision check is done for the pushed entity against other entities. Well it should be easily understandable.
But what you may not know is that the pushed entity also checks collision against itself. As the result, if the "distance" of the "push-back" effect is too short, it will not be able to push entities that have large colliders.
(Because the devs are using the collider size as "weight"?)
A request for "fixing" this issue has been made: Lower the minimum distance of push-back effect. But no further news is heard.
Data and Control
Data structure in data vs controlWhile you can access the actual data table of each prototype in data phase using data.raw, you can't do this in control phase.
You have to rely on the API listed on http://lua-api.factorio.com/latest/, because the data has been processed and formatted by the game.Code accessibility in data vs controlThere is big difference between the accessibility of the code defined in data phase and that of the code defined in control phase.
- Data: any non-local variable or function you defined in data phase can be shared to the mods that are loaded after yours without the need of "require". For example, mod A defined function abc(). Mod B depends on mod A so it is loaded after A, and hence it can also call A's abc().
While this is convenient, you may need to take extra care about overriding. Say, mod A defined abc() in data.lua, mod B also defined its abc() in data.lua, if mod A calls abc() in data-updates.lua or data-final-fixes.lua, B's abc() will be executed instead of A's.
Therefore, keep your variables and functions local unless you want to share them with other mods.
- Data-Control: the variables and functions in data phase are not accessible in control phase. But there are exceptions*.
- Control: each mod is in its own namespace. Mod A's foo() defined in control.lua will not be accessible by mod B. But there are exceptions*.
*Exceptions: some variables and functions defined in Factorio/data/core/lualib are always accessible by all mods in both data and control phase. For example, data, data:extend(otherdata), util, util.oppositedirection(direction), table.deepcopy(object), etc.
(Please take extra care about "util". As it is a very common word, you may want to build up your own util object for your mod byBut this is wrong, as you are overriding the whole util object provided by Factorio. Error will be thrown when other mods use the original functions.Code: Select all
util = {} function util.foo() ... end
A solution is replace the first line byBut the best solution is define a local util with different name, likeCode: Select all
util = util or {}
Code: Select all
local my_util = {}
Useful functions and librarieslog(string) - log the message into factorio-current.log
error(string) - throw an error, just like when a mod has done something wrong.
serpent - a library that can be used as pretty printer in Factorio, and it is already included in the engine. No "require" is needed. Document can be found here: https://github.com/pkulchenko/serpent.
If you are messing with prototypes that are created or changed by other mods but you are not sure what they are doing, you can use the following to see the final data table:Code: Select all
log(serpent.block(data.raw[type][name]))
LuaEntityPrototype.collision_mask does not return not-colliding-with-itselfAlthough you can add "not-colliding-with-itself" to entity collision_mask in data phase, it is actually not a mask, but a property to collision mask. When you are reading LuaEntityPrototype.collision_mask in control phase, you will find out that "not-colliding-with-itself" is never returned.
A quick demonstration:The above comment will print "nil" instead of "{not-colliding-with-itself}"Code: Select all
/c game.print(serpent.block(game.entity_prototypes["rocket-silo-rocket"].collision_mask))
Control
The global tableEven though it is documented, but I think many modders have still believed wrongly that the global table is shared across mode, due to its name "global".
But no. Each mod has its own global table. Mods can only share things using the LuaRemote class.
So, yes, you don't need to worry about the other mods when using the global table.
(It is unnecessary to add an extra object to save data and save the object to global, like how I did in Creative Mode. )
e.g. You don't needInstead, it is safe to doCode: Select all
global.mod_id.foo = 1
Code: Select all
global.foo = 1
Only one handler for each eventCurrently, only one handler can be added for each event in each mod. If you add more than one handlers to an event, only the latest one will be called.
Meaning that if you do the following:You will notice that only "b" is printed when you build an entity.Code: Select all
script.on_event(defines.events.on_built_entity, function() game.print("a") end) script.on_event(defines.events.on_built_entity, function() game.print("b") end)
If you really want to use multiple handlers, the solution is to make your handlers into functions, and call them in the actual event handler, likeThen, both "a" and "b" will be printed.Code: Select all
function print_a() game.print("a") end function print_b() game.print("b") end script.on_event(defines.events.on_built_entity, function() print_a() print_b() end)
This concept can apply to how you unsubscribe an event. In fact, you are adding "nil" as the handler, which override the original one.Code: Select all
script.on_event(defines.events.on_built_entity, nil)
GUI NamingSimilar to Prototype Naming, it is recommended to also add prefix to your GUI element names. There are 2 reasons for that:
The first one is rather obvious. GUI elements under the same parent have to have unique names, or error will be thrown.
The second reason may be a bit tricky. Events, including those on_gui events, are broadcasted to every mods. If multiple mods have created different buttons but given them the same name, when a player click on any of these buttons and on_gui_click is invoked, the mods cannot distinguish the buttons easily with event.element.name. Things will get messy.Setting player.characterIf you want to, for example, disable god mode by doing the following:make sure the character entity is on the same surface as the player or an error will be thrown.Code: Select all
player.character = character_entity
Also, you should also note that teleportation across surfaces using LuaControl.teleport only works on players at this moment. You can't teleport the character entity to the surface of the player, or error will be thrown.How to order deconstruct tilesLuaEntity.order_deconstruct(force) won't work on tiles.
Instead, you create a proxy (marker) to tell the robots to deconstruct the tile:Code: Select all
surface.create_entity { name = "deconstructible-tile-proxy", position = tile.position, force = your_force }
The cause in on_entity_died does not always workKilling worms or any other turrets will trigger on_entity_died, but without the cause.Rseding91 on Discord wrote:Turrets don't track the killer because they don't die on 0 health. They switch to a death animation and when that finishes then they die.Find entity using position will not return entities without colliderWhen you use LuaSurface.find_entity, LuaSurface.find_entities, LuaSurface.find_entities_filtered or LuaSurface.count_entities_filtered, please be noted that:
Note: the space rocket (rocket-silo-rocket) does have collider. It can be found by position-search.
- Position-search will NOT return entities that don't have collider, like smoke, non-directional projectile, explosion, flying-text, etc.
- i.e. LuaSurface.find_entity will never return those entities.
- Area-search will count all entities including those that don't have collider.
- i.e. LuaSurface.find_entities will also return those entities.
We don't know why it is implemented like this. A request for standardizing them has been made (LuaSurface::find_entity returns no-collision entity), but no further news is heard.
Feel free to contribute if you know more and you think that should be added.The Mod Portal
Character limitBefore you decide to write your very detailed mod description in the "Information" block on Mod Portal, there is one thing you should know first: you can't write more than 10000 characters, or it will show an error when you click the submit button.
While it is convenient for the users to read mod description directly on Mod Portal, as a side note, this forum allows up to 60000 characters per post.New PortalSource: viewtopic.php?f=189&t=38238&p=228951#p228951And this is already in Factorio Roadmap for 0.16+:Klonan wrote:We have someone working on a completely better mod portal as we speak,So, be patient.Mod portal improvement (Rewrite)