Modding: Lua Scripting

From Noita Wiki
Jump to navigation Jump to search
Modding Navigation
Fundamentals
BasicsData.wakGetting startedLua ScriptingUseful Tools
Guides
AudioEnemiesEnvironments (Fog of War) • Image EmittersMaterialsPerksSpecial BehaviorsSpellsSpritesheetsSteam WorkshopUsing CMake
Components/Entities
Component DocumentationEnumsList of all tagsSpecial TagsTags SystemUpdate Order
Lua Scripting
Lua APIUtility Scripts
Other Information
Enemy Information TableMagic NumbersSound EventsSpell IDsPerk IDsMaterial IDs


Basic info and examples for writing Lua scripts for Noita mods. Note that scripting is only a part of modding, be sure to read about the Entity Component System as well.

Places to hook your scripts

init.lua

  • The best place to get started.
  • Doesn’t need any overwriting, this is specially unique to your mod.
  • See Modding Basics

data/scripts/gun/gun.lua

  • Called on every wand shot, highly intertwined with the engine code.
  • Note compatibility, as explained below

data/scripts/biomes/<biome_name>.lua

  • Called once when entering the biome
  • Note compatibility, as explained below

<LuaComponent>

  • Probably the best and most-used place to hook your random code
  • Most everything else that you can find under data/scripts/ is actually defined in some entity via this component.
  • See the official lua_api_documentation.txt for a list of how to hook into different actions with this component.

Hooking in a compatible way

When dealing with common scripts used heavily by the game itself and other mods, it might be a good idea to append to it, instead of simply overwriting it via the data/ path.

When every modder is doing this, their code will get appended in the player's mod loading order, and nothing is forcefully overwritten.

-- Any file can be appended by any file, the gun.lua is just an example.
-- *But* this can only be done inside init.lua.
ModLuaFileAppend("data/scripts/gun/gun.lua", "mods/mymod/files/gun.lua")

Lua Notes and Tips

Restricted Libraries

Some of the default Lua libraries are restricted and will not run in the mod files. A incomplete list of restricted libraries and functions:

  • IO
  • OS
  • require

Some of the functionality of these libraries has been reimplemented in the Lua API.

-- You can't do this
local file = require "/mods/MODNAME/files/somefile.lua"
-- But you can do this
local file = dofile_once("/mods/MODNAME/files/somefile.lua")

-- You can't do this
local time = os.time()
-- But you can do this
local year,month,day,hour,minute,second = GameGetDateAndTimeLocal()
local time = year .. month .. day .. hour .. minute .. second

Without the IO library, it is far more difficult to save persistent data between runs. The only two ways to store persistent data are by using flags or the mod_settings file.

For more details, check the Restricted API.

Script examples

Get the current entity

local entity = GetUpdatedEntityID()

"Current" means the entity in which your script is running via a <LuaComponent> (or other such trigger). Not all scripts are run this way, for example init.lua.

In the above snippet the entity could be player, wand, projectile, anything; depending on where the script is run from. This is the easiest way to fetch the currently running entity, so it deserves a mention.

Bonus: in data/scripts/gun/gun.lua this will give you the player entity.

Get player entity

The basic utility script returns a list of player entities, so we have to jump through a small hoop to get just one.

For brevity, the fetching of player entity is left out in subsequent examples. Many hooks expose the player entity already, but this can be used as a backup if they don't.

dofile_once("data/scripts/lib/utilities.lua")

function get_player()
  local players = get_players()
  if players then
    return players[1]
  end

  -- Player is dead
  return nil
end

local player = get_player()

Get currently held wand

dofile_once("data/scripts/gun/procedural/gun_action_utils.lua")

-- Probably works on any entities with the Inventory2Component, eg. some enemies
local wand = find_the_wand_held(player)

Get currently held item (including wands)

function get_held_item(animal)
  local inv_comp = EntityGetFirstComponentIncludingDisabled(
    animal, "Inventory2Component"
  )

  -- Although the component should always be present, something unexpected
  -- might've still happened (eg. another mod messing around).
  if not inv_comp then
    return nil
  end

  return ComponentGetValue2(inv_comp, "mActiveItem")
end

-- Should return a numeric ID
local wand = get_held_item(player)

Easily add items to inventory

-- Note, player is not the only thing with an inventory
function add_items_to_inventory(player, items)
  for _, path in ipairs(items) do
    local item = EntityLoad(path)
    if item then
      GamePickUpInventoryItem(player, item)
    else
      GamePrint("Error: Couldn't load the item ["..path.."]!")
    end
  end
end

function OnPlayerSpawned(player)
  local items = {
    "data/entities/items/pickup/thunderstone.xml",
  }
  add_items_to_inventory(player, items)
end

Spawn and enable perk

-- Perk system is a bit convoluted, so use the existing function
dofile_once("data/scripts/perks/perk.lua")

local perk = perk_spawn(x, y, "PROTECTION_RADIOACTIVITY")

-- Leave this part out, if you only want to spawn the perk in world
if perk then
  perk_pickup(perk, player, EntityGetName(perk), false, false)
end

Spawn most other things

-- Enemy
EntityLoad("data/entities/animals/duck.xml", x, y)

-- Gold
EntityLoad("data/entities/items/pickup/goldnugget_200.xml", x, y)

Run init code only once

ie. only upon starting a new game, not when you load a savegame

local LOAD_KEY = "MYMOD_FIRST_LOAD_DONE"

function OnPlayerSpawned(player)
  if GlobalsGetValue(LOAD_KEY, "0") == "1" then
    return
  end
  GlobalsSetValue(LOAD_KEY, "1")

  -- Continue with your code normally here
end

Shoot a projectile through Lua

dofile_once("data/scripts/lib/utilities.lua")

-- Define your starting position here, according to whatever rules you want,
-- and same with the velocities.

shoot_projectile(player, "mods/mymod/files/projectiles/supernuke.xml", from_x, from_y, vel_x, vel_y)

Note that there is also the direct API function GameShootProjectile(), but it requires a few lines of setup, which the included utility function already does for you.