In other languages: 中文

Tutorial:Modding tutorial/FreeER

From Official Factorio Wiki
Jump to: navigation, search
Time.png
Modding tutorial/FreeER has been archived.
The information on this page pertains to a previous version of the game.
It may be incorrect, or concern a removed/changed feature.

There is a thread about this pages: http://www.factorioforums.com/forum/viewtopic.php?f=25&t=4570

Please refer to it, if you have questions.

Partial update to 0.11/MP (plus rough tut notes) was made and shared in this post

FreeER's Step By Step Guide to Modding Factorio (Created for Factorio 0.4.1, updated to 0.10.3 by jeroon, updated to 0.14.22 by Maniah (Work in Progress))

This is my [FreeER's] Step by Step guide to how to create a mod, within this guide I will be creating a mod and you can follow along or simply read and see how it works. I will also upload at the start of each 'chapter' the images I use and the completed code.

The first step to modding is to decide to learn how and the second is to actually learn how :)

So if you are here I'm going to assume that you have decided to learn how to mod Factorio and are ready to learn!


Preparation and resources

First things first though, I am not an expert on either Factorio or Lua :)

Also, some basic warnings:

  • BEWARE OF POSSIBLE CHANGES, Factorio is in alpha and constantly updated, and this guide might not always be updated to the latest version.
  • BACKUP THE GAME BEFORE YOU START MODDING, although not likely, during the modding process the game might crash and corrupt your files. Make sure you have something to go back to.

Some helpful tips when modding:

  • If something is written here and does not work, first make sure that it is written exactly as it appears here (or download the chapter zip) and if you are using a later version than the last tutorial update, check the latest notes on the update to see what/if something changed.
  • It may be helpful to look through the base code in the ../factorio/data/base directory.
  • You can also use the official Factorio lua API documentation.

All modding in Factorio is done in the Lua programming language.

Now before we get started you need to know that every mod (including the majority of vanilla Factorio) adds items, entities, technologies, and scripting (in-game code/actions) using Lua.

  • You can find a tutorial on the Lua programming language itself here, it can be very useful if you are new to lua or programming in general :) For more detail you can look through the 1st edition of the Programming in Lua (PIL) book here.
  • Lua files can be created and edited in any text editor. Well known ones that offer syntax highlighting are Notepad++ and TextPad. Popular programs used for the creating of images and icons are Photoshop and Paint.net.


Chapter 1 - Setup

[The mod so far (download not updated for 0.14)]

Now to get started, first you need to find your Factorio installation folder. This page has all the information on how to find it. On a fresh copy of Factorio, there is no mod/saves folder yet. You have to play the game first, and save manually.

To start creating this mod we need an idea, I think I'll try my hand at creating a mod which adds bombers to the game. Why? Because who has not wanted to simply fly over the top of an enemy spawner, drop a bomb, and watch the destruction unfold :) Plus, no one else has created it and it will include all of the steps needed (eventually) for making even the most complex Factorio mod.

1) In the mods folder, create a new folder called 'BomberTutorial_0.1.1'. The name of the folder must always be the name, and underscore and the version found in the info.json file (see step 3).

2) In the BomberTutorial_0.1.1 folder, create 3 files:

- info.json (information about the mod, the maker and the version)
- data.lua (this contains definitions of what other .lua files to load)
- control.lua (actions we want the mod to perform)

3) Edit the info.json file with a text editor, and type (or copy/ paste):

{
  "name": "BomberTutorial",
  "version": "0.1.1",
  "factorio_version": "0.14",
  "title": "FreeER's Step by Step Bomber Mod",
  "author": "FreeER",
  "contact": "http://www.factorioforums.com/forum/viewtopic.php?f=25&t=4570",
  "homepage": "http://www.factorioforums.com/wiki/index.php?title=Modding_Tutorial",
  "description": "Mod that adds a bomber to Factorio"
}

4) In the BomberTutorial_0.1.1 folder, create a folder called 'prototypes'. This is where all new items, recipes and technologies go, that we want to use in our mod. In the next chapters we will create the files that go into this folder.

We now (technically) have a mod! It doesn't do anything yet, but it is a mod.

5) Save all files and start Factorio. Click the Mods button in the main menu, and it now shows our mod! Since the mod doesn't do anything yet, we can close Factorio now.


Chapter 2 - The Items

[The mod so far (download not updated for 0.14)]

Now it is time to add the first items to the mod. Items are the objects that will show up in an inventory (whether that is the players, a chest, or anything else). To start with we will need at least the bomber, and maybe a bomb to go with it.

1) In the prototypes folder, create a file:

- item.lua (this is where we can add new items to the game)

2) edit the item.lua file and add:

data:extend({

 {
    type = "item",
    name = "bomber",
    icon = "__BomberTutorial__/graphics/icon_bomber.png",
    flags = { "goes-to-quickbar" },
    subgroup = "ammo",
    place_result="bomber",
    stack_size= 1,
  },
  {
    type= "item",
    name= "bomb",
    icon = "__BomberTutorial__/graphics/icon_bomb.png",
    flags= { "goes-to-main-inventory" },
    subgroup = "ammo",
    order= "c-d-b",
    stack_size= 5,
  }

})

Now we've added 2 items, a bomber and a bomb. Note the comma between the 2 items. A brief explanation of what we've just added:

- type: shows what we want to add to Factorio
- name: the name has to be consistent between items, recipes, tech tree etc
- icon: the location of the icon that shows in the construction panel. Base directory is __modname__ , we will add the graphics folder in the next step
- flags: where to put the item once it is created (goes-to-quickbar or goes-to-main-inventory).
- subgroup: what tab of the construction panel does the icon show. Since there is no group yet for Fighter Planes, this is set to Ammo for the plane as well. Not setting a group will result in an extra tab called '?'. Names are in ..data\base\prototypes\item\item-groups.lua
- place_result: what item will be created. There can be multiple items here.
- order: determines the position of the icon in the construction panel. See kovarex' post and ..data\base\prototypes\item\item-groups.lua
- stack_size: up to what number does the item stack in your inventory.

3) In the BomberTutorial folder, add a folder called "graphics". This is where all images, sprites and icons go.

4) Copy the files from this zip file to the graphics folder.

Now we have our items, but we can't create them yet! We need to set up recipes if we want players to be able to use them!

Don't start the game yet, because there won't be anything to see, and the game might actually throw some errors at you.

Chapter 3 - The Recipes

[The mod so far (download not updated for 0.14)]

Let's create the recipes so that players can craft our bomber, and the bombs. A recipe is what shows in the construction panel.

1) In the prototypes folder, create a file:

- recipe.lua

2) In recipe.lua, type:

data:extend({
 {
    type = "recipe",
    name = "bomber",
    enabled = "true",
    ingredients = 
    {
      {"iron-stick",50},
      {"electronic-circuit",50},
      {"iron-gear-wheel",50},
      {"iron-plate",200}
    },
    result = "bomber"
  },
  {
    type = "recipe",
    name = "bomb",
    enabled = "true",
    ingredients = 
    {
      {"electronic-circuit",10},
      {"iron-plate",20},
      {"explosives",5}
    },
    result = "bomb"
  }
})

This makes our bomber cost 50 iron sticks (supports), 50 electronic circuits (electronics of course), 50 iron gears (landing gears, bomb doors, etc.) and 200 iron plates (the body) and our bombs 10 electronic circuits (you want safety checks right?), 20 iron plates (the body) and 5 explosives (for a good byebyekaboooom!).

There are two important lines:

name: this line has to be the same name we used in the item.lua
enabled: determines if the item is visible from the start, or needs research first. We will make this 'true' for now, so we can test.

The exact names for the ingredients can be found in ..data\base\prototypes\item\item.lua (and the other files in that folder)

We still can't start the game, because now that we have both our items and their cost determined, it's time to make the bomber something that we can actually use!


Chapter 4 - The Entities

[The mod so far]

The entity file describes the behaviour of the bomber, health, speed, weight, the lot. Let's start by creating the file:

1) In the prototypes folder, add a file called:

- entity.lua

2) Edit the entity.lua file and add:

data:extend({
  {
    type = "car",
    name = "bomber",
    icon = "__BomberTutorial__/graphics/icon_bomber.png",
    flags = {"placeable-player", "player-creation", "placeable-off-grid", "not-on-map"},
    minable = {mining_time = 1, result = "bomber"},
    max_health = 2000,
    corpse = "medium-remnants",
    collision_mask = {},
    dying_explosion = "medium-explosion",
    energy_per_hit_point = 1,
    crash_trigger = crash_trigger(),
    selection_box = {{-0.7, -1.2}, {0.7, 1.2}},
    collision_box = {{-0.7, -1.2}, {0.7, 1.2}},
    collision_mask = {}, -- empty table means collides with nothing
    effectivity = 0.5,
    braking_power = "20kW",
    burner =
    {
      effectivity = 0.1, -- 1/10 of consumption power gets to car
      fuel_inventory_size = 2,
      smoke =
      {
        {
          name = "smoke",
          deviation = {0.25, 0.25},
          frequency = 50,
          position = {0, 1.5},
          slow_down_factor = 0.9,
          starting_frame = 3,
          starting_frame_deviation = 5,
          starting_frame_speed = 0,
          starting_frame_speed_deviation = 5
        }
      }
    },
    consumption = "250J",
    friction = 0.00001,
    light =
    {
      {
        type = "oriented",
        minimum_darkness = 0.3,
        picture =
        {
          filename = "__core__/graphics/light-cone.png",
          priority = "medium",
          scale = 2,
          width = 200,
          height = 200
        },
        shift = {-0.6, -14},
        size = 2,
        intensity = 0.6
      },
      {
        type = "oriented",
        minimum_darkness = 0.3,
        picture =
        {
          filename = "__core__/graphics/light-cone.png",
          priority = "medium",
          scale = 2,
          width = 200,
          height = 200
        },
        shift = {0.6, -14},
        size = 2,
        intensity = 0.6
      }
    },
    animation =
    {
      layers =
      {
        {
        filename = "__BomberTutorial__/graphics/sheet_bomber.png",
        line_length = 8,
          width = 211,
          height = 211,
          frame_count = 1,
          axially_symmetrical = false,
          direction_count = 16,
          shift = {0, -0.1875},
          animation_speed = 8,
        }
      }
    },
    turret_rotation_speed = 0.35 / 60,
    stop_trigger_speed = 0.2,
    stop_trigger =
    {
      {
        type = "play-sound",
        sound =
        {
          {
            filename = "__base__/sound/car-breaks.ogg",
            volume = 0.6
          },
        }
      },
    },
    crash_trigger = crash_trigger(),
    sound_minimum_speed = 0.2;
    working_sound =
    {
      sound =
      {
        filename = "__base__/sound/car-engine.ogg",
        volume = 0.6
      },
      activate_sound =
      {
        filename = "__base__/sound/car-engine-start.ogg",
        volume = 0.6
      },
      deactivate_sound =
      {
        filename = "__base__/sound/car-engine-stop.ogg",
        volume = 0.6
      },
      match_speed_to_activity = true,
    },
    open_sound = { filename = "__base__/sound/car-door-open.ogg", volume=0.7 },
    close_sound = { filename = "__base__/sound/car-door-close.ogg", volume = 0.7 },
    rotation_speed = 0.015,
    weight = 700,
    guns = { "submachine-gun" },
    inventory_size = 80
  }
})

This sets our bomber up to be a car entity (since it is currently the only entity type that can be driven (without rails)), with no collision box and a small (cockpit size) selection box. If we had left the collision box on (like the car has), then the bomber would have been able to hit trees, which is not what we want from a plane. The speed is increased, and a, fairly large, amount of pollution (emissions) is added.

These are not all options that are available, for more properties you can check out the section that creates the car in the vanilla game ( /data/base/prototypes/entity/entities.lua in the folder you installed the game in)

You can play around with these settings to see what they do, some might need a bit more research and trial and error to understand.

Now we've created something that we can actually use! We have the 3 basics: item (the definition), recipe (the cost) and entity (the details), so it's time to tell the game what files it has to load.

3) Edit the data.lua file in the BomberTutorial folder, and add:

require("prototypes.item")
require("prototypes.recipe")
require("prototypes.entity")

just these lines, nothing else. This tells the game to load all 3 files we've just created.

4) Save all files, start Factorio, start a new game or load a save game and try to find the bomber and bomb icon in the Combat tab. You can build the bomber, place it, load some bombs in the inventory, some fuel, and fly off! If you don't have the materials to build it, you can open the console (should be ~ or /, though I have read that it can be ñ on some, Spanish, keyboards) and type

/c game.player.character.insert{name="bomber", count=1}

This will give you a plane to play with! You'll need coal as well, simply replace "bomber" with "coal" and change the count to a suitable number.



If you make changes to your mod, you have to restart Factorio to see the changes. When Factorio is starting, it will tell you if it detects any errors in your mod's code.



When you are done flying, head on back and we'll set up the technologies so that the bomber/bombs can actually be researched and crafted :)

Chapter 5 - The Technologies

[The mod so far]

First we have to change something we made earlier: the bomber is now craft-able without research. To change this, we have to go back to the recipe.lua file from Chapter 3, in the prototypes folder.

1) Open recipe.lua, and find the following lines (for both bomber and bomb):

enabled = "true"

and set them to:

enabled = "false"

This means both bomber and bomb will only be available after you research them, which we will add now.

2) In the prototypes folder, create a new file:

- technology.lua

3) Open the technology.lua file, and add:

data:extend({
  {
      type = "technology",
      name = "bombertech",
      icon = "__BomberTutorial__/graphics/icon_bomber_tech.png",
      effects =
      {
        {
            type = "unlock-recipe",
            recipe = "bomber"
        },
        {
            type = "unlock-recipe",
            recipe = "bomb"
        }
      },
      prerequisites = {"flying"},
      unit =
      {
        count = 10,
        ingredients =
        {
          {"science-pack-1", 2},
          {"science-pack-2", 1},
          {"science-pack-3", 1}
        },
        time = 20
      }
  }
})

This gives a research called bombertech. This will, when researched, unlock both our bomber and the bomb to go with it. Notice that the icon image was already copied to the graphics folder earlier on.

Some important lines:

prerequisites: this states that flying has to be researched before you can start researching bombertech
count: the number of science packs needed (multiplied by the number after each science pack)
time: the time needed to research one unit

4) Open data.lua in the BomberTutorial folder, and add:

require("prototypes.technology")

so the game will load the technology.lua as well.

5) Start the game and you will see the bombertech technology in the List of Technologies. When you've researched it, bomber and bombs once again become available in the Combat tab of the Crafting screen.

When you hover over the technology, or the icons in the Crafting screen, you will notice there's something missing: descriptions. We'll fix this in the next chapter.


Chapter 6 - Naming and localizations

[The mod so far]

To allow fast translations into different languages, all names used in the lua files so far can be collected and put in a file of the desired language. To get started:

1) In the BomberTutorial folder, create a new folder called "locale". Inside this locale folder, create a new folder called "en" for english. Inside this en folder, create a file called:

- locale.cfg

2) Open the local.cfg, and add:

[item-name]
bomber = Bomber
bomb = Bomber bomb

[item-description]
--bomber = Stealth Bomber
bomb = Bomber ammunition 

[technology-name]
bombertech = Bomber Flight

[technology-description]
bombertech = Unlock the marvels of bombed flight!

Notice the 4 different categories in squared brackets, and the names we've used so far beneath them.

The locale.cfg file should be saved in the UTF-8 without BOM format or Factorio will not recognize the characters properly, usually you'll get an error about no '=' signs or that an '=' was expected and it found an '='; Pretty weird error messages, at least now you know why you're getting them!

3) Save the file and start the game again.

Now if you look at the Technology screen, and hover over the bomber icon, you will see it now has the right name on top, the name of the mod (BomberTech) in yellow in the middle, and the description at the bottom. When you research the bomber, and check the Crafting screen, you will see the same for the bomber and bomb icons. Notice how bomber has no description, because it is commented out in the local.cfg file. (The bomber isn't really stealthy anyway ;) )

And with that we are done right? Well if we were what did we create bombs for? As you may notice the plane can not use them. They are also not part of any crafting recipe so the bombs are utterly useless, let's fix that!


Chapter 7 - The Lua Code

[The mod so far]

It is time to make those bombs we created earlier into something we can actually kill those annoying creepers with!

1) Open control.lua in the BomberTutorial folder and type/paste:

require "util"
require "defines"

--The code for FreeER's Step By Step Guide to Modding Factorio (Created for Factorio 0.3.x Updated to 0.10.x) AKA Bomber Mod
game.oninit(function()
glob.bomber = 0
end)

game.onevent(defines.events.ontick, function(event)
  if game.player.character and game.player.character.vehicle and game.player.character.vehicle.name == "bomber" 
and game.player.character.vehicle.getinventory(2).getitemcount("bomb") >= 1 and event.tick-glob.bomber >= 180 then
    local bomb = game.findentities{{game.player.character.vehicle.position.x-5,game.player.character.vehicle.position.y-5},
{game.player.character.vehicle.position.x+5,game.player.character.vehicle.position.y+5}}
    local drop = false
    local biters = 0
    for k,v in pairs(bomb) do
      if v.force.equals(game.forces.enemy) then
        drop = true
        if v.name == "small-biter" or v.name == "medium-biter" or v.name == "big-biter" then
          biters = biters + 1
          if biters < 5 then --if five or more will be killed then drop
            drop = false
          end
        end
      end
    end
    if drop then
      glob.bomber = event.tick
      for k,v in pairs(bomb) do
        if v.force.equals(game.forces.enemy) then
          if v.health then
            v.die()
          else
            v.destroy()
          end
        end
      end
      game.player.character.vehicle.getinventory(2).remove{name="bomb", count=1}
    end
  end
end)

The first two lines "require ..." tells the game that our script requires the util.lua and defines.lua files to be loaded as well. These are provided by the developers of Factorio, util gives access to some useful functions (like distance between two points and formattime), though honestly it's not used in this script. And defines lists, among other things, the events and inventories (though not all inventories are listed at this time, which is why I had to use the console to test and determine which number for the bomber would give use the results for the bomb), feel free to check both of them out (they are located in /data/core/lualib/)

The "game.oninit(function()" is used to set up our global variable (bomber) in the table glob (which is a special table that will be loaded by Factorio every time you load your save game).

The "game.onevent(defines.events.ontick, function(event)" is where the main code is. This block of code is where the we check if the player is not in god or ghost mode (player.character), is in a vehicle (.vehicle), if that vehicle is the bomber, and if the bomber has bombs and if it is then it checks if it is over an enemy (since we can not, yet, execute code on a key press) and if it is, it counts if there are more than five biters, then it will any enemy entity within range. (There is a bug here btw, see if you can find it)

That's it! You now have a fully functioning mod that adds bombers, bombs, and let's you fly over spawners raining death upon all in the vicinity!


Chapter 8 - Expanding the mod!

[The mod so far]

'Note: this portion has not been updated yet!'

Well now that we have a fully functioning mod, let's see if we can add a bit more to it :) But first, I'd like to rearrange the layout. I personally dislike having everything in the data.lua file unless it is a really small mod. So,

1) Make two folders, one called graphics and one called prototypes.

2) Move the picture files into the graphics folder and copy the data.lua file into prototypes

3) Make four copies of the data.lua file inside of prototypes using the names, entity.lua item.lua recipe.lua and technology.lua

4) In the original data.lua file delete everything and replace it with

require("prototypes.entity")
require("prototypes.item")
require("prototypes.recipe")
require("prototypes.technology")

This way instead of having everything in data.lua, the data.lua file simply tells Factorio to load up the files that contain the real data.

5) in each of the prototype files delete everything that does not deal with the file name, for instance inside of the item.lua file the only lines you would have left are

data:extend({
	{
        type= "item",
		name= "bomber",
		order= "b-c-a",
        place_result="bomber",
		stack_size= 1,
		flags= { "goes-to-quickbar" },
		icon="__bomber__/graphics/bomber.png"
	},
    {
		type= "item",
		name= "bomb",
		order= "b-c-a",
		stack_size= 5,
		flags= { "goes-to-main-inventory" },
		icon="__bomber__/graphics/bomb.png"
	}
})

6) Now that we have a better layout (in my opinion) let's fix that bug from the control.lua shall we? Did you find out what it was? To fix it try doing this instead:

    for k,v in pairs(bomb) do
      if v.force.equals(game.forces.enemy) then
        if v.type == "unit" then
          biters = biters + 1
        else
          if not drop then drop = true end
        end
      end
    end
    
    if not drop and biters>5 then drop=true end

Let's make some biters into friends, well if not friends then at least terrified of us! 7) Make a new bomb item called 'FriendMaker' (or whatever you like) 8) Make a recipe for the FriendMaker, I used 1 bomb and 20 science-pack-1's...

9) Open the control.lua in the main folder of the mod and change

game.player.character.vehicle.getinventory(2).getitemcount("bomb") >= 1

to

(game.player.character.vehicle.getinventory(2).getitemcount("bomb") >= 1 or game.player.character.vehicle.getinventory(2).getitemcount("FriendMaker") >= 1)

10) In control.lua change the if drop = then glob.bomber... code to

if drop then
      glob.bomber = event.tick
      for k,v in pairs(bomb) do
        if v.force.equals(game.forces.enemy)
          if game.player.character.vehicle.getinventory(2).getitemcount("FriendMaker")<=0 then
            if v.health then
              v.die()
            else
              v.destroy()
            end
          else
            v.force=game.player.force
            if v.type=="unit" then v.setcommand{type=defines.command.wander, distraction=defines.distraction.byenemy} end
          end
        end
      end
      if game.player.character.vehicle.getinventory(2).getitemcount("FriendMaker") then game.player.character.vehicle.getinventory(2).remove{name="FriendMaker", count=1}
        else game.player.character.vehicle.getinventory(2).remove{name="bomb", count=1}
      end
    end

The FriendMaker will now turn all enemies in it's range into allies :) It will also be used before any normal bombs.

Chapter 9 - Improving the mod?

Well, now we have a working bomber and two bombs for it, but the player can not control the bombs. If they wanted to switch to using regular bombs instead of using the FriendMaker's they would have to open the bomber's inventory and remove the FriendMaker's. That's not very useful is it? There are three ways (that I can think of) to deal with this, make the bombs into ammo for new weapons that get swapped out when the player enters the bomber, or make the bombs into consumables (like the grenade and poison capsule), or make use guis that the player clicks to drop a bomb...I think I'll leave it up to you guys to work these out (unless someone asks me to do it :))

note: bblewittAG has informed me [FreeER] that there is a bug with displaying raw material counts when the mod is in 'folder' form and not a zip archive. To solve that install a program like 7zip (p7zip on linux) and right click the folder and make it a zip (on Windows you could probably use "Send to"->"compressed (zipped) folder" without any additional programs, but I personally use 7zip because it handles most archive filetypes I've ever seen quite well), then delete the folder version (you can extract the zip later if you need to edit it again). This solved the problem for him on version 0.10.12. An additional note is that past version 0.11.x of Factorio the folder name (and zipped folder name) will need to include the version specified in the info.json file as part of the name. Ex. bomber_0.1.1.zip (assuming the info.json name is "bomber" and the version is "0.1.1")