Modding Pitfalls

From Avorion Wiki
Jump to: navigation, search

With all its power and complexity, the Avorion Scripting API and its environment comes with a few pitfalls that you simply have to be aware of to avoid them.

Script Path Caching[edit | edit source]

This behavior is also only important when you create new script files while Avorion is running.

In order to save performance and avoid disk I/O, the paths to scripts and script extensions are cached. So when the game once accessed a script and its modded extensions, it remembers those file paths and will reuse them whenever the same script is loaded again. Newly added extensions and scripts that were added while the game was already running might be skipped.

In a scenario where there are, say, 100 mods installed, this behavior significantly improves script-loading performance because the game doesn't have to search each mod folder for potential extensions of scripts whenever a script is loaded, but just once per script.

You can disable this behavior by checking the Dev Mode check box in the Mods window. This will impact performance somewhat when loading scripts, but it will make sure that the game always checks all possible files that are available on disk, without reusing previous file paths.

Note: This only affects file paths and extensions, not the content of the files.

References instead of New Objects[edit | edit source]

Some instantiations of objects create only a reference to the actual object. This is true for a lot of objects, some of the most common ones are:

  • Entity([id]) creates a reference to an existing, not a new Entity. Use Sector:createEntity(...) to create new entities.
  • CargoBay() (and other component classes) create a reference, not a new component. New components are created when entities are created.
  • Label([index]) (and other UI classes) create a reference to an existing, not a new UI element. Use the UIContainer:createLabel(...) etc. functions to create new UI elements.
  • Faction([index]), AIFaction([index]), Player([index]) or Alliance([index]) create a reference to an existing Faction, not a new one. Use the Galaxy():createAIFaction() functions to create new AI factions. You cannot create new Players or Alliances.

New Objects instead of References[edit | edit source]

Sometimes it's also the other way around: What looks like a reference to an object is actually a copy of an object. Due to internal architecture and how lua does interfacing of classes, some properties that look like they return a reference to a value, actually return a copy.

local mat = Matrix()
mat.position.x = 5 -- mat.position returns a copy of the 'position' vec3 variable, so we're modifying a temporary copy here
mat.position.y = 0
mat.position.z = 2

print (mat.position.x); print (mat.position.y); print (mat.position.z)
prints the following: 
0
0
0

Instead, you should do the following when you want to assign the position of the matrix:

local mat = Matrix()
mat.position = vec3(5, 0, 2) -- this assigns a vec3 to the mat.position variable

print (mat.position.x); print (mat.position.y); print (mat.position.z)
prints the following: 
5
0
2

How to avoid[edit | edit source]

This error is easy to avoid when you're aware of the situations where it can appear. For example, when you see an assignment where there are two dots on the left side like this:

mat.position.x = 5 
-- or 
a.b.x = 5
-- or 
a:b().x = 5

and you're using API classes then it's almost 100% sure to be the above case.

Entity Reference Invalidation[edit | edit source]

When a script changes sectors, for example because it's attached to an Entity or a Player that changes sectors, all of its Entity objects are invalidated. Entity is only a reference to an entity in a sector, and since scripts can't access things that are outside their current sector, existing Entity objects are invalidated.

local entity

function initialize()
    entity = Entity()
end 

function update()
    -- this will fail after a sector change because 'entity' was invalidated
    print(entity.name)

    -- this will always work, since the Entity will always be newly created and thus always be valid
    local e = Entity()
    print(e.name)
end

If you want to create an Entity and need regular access to it, regardless of sector changes, you should save the Entity's id instead, and use Entity(id) to access it.

Client-Script Resets[edit | edit source]

Due to technical reasons, client-sided Sector and Entity scripts are deleted and recreated every time a sector change is done. This includes the player's ship!

UI Lazy Initialization[edit | edit source]

In order to save memory, Avorion uses lazy initialization for scripted UI. So your initUI function will only be called when the player actually interacts with the entity.

initialize() when loading from disk[edit | edit source]

In scriptable object scripts, when they're being restored, their initialize() function will also be called when loading the object from disk - but without parameters. When the initialize() function is called during a restoration, a global variable _restoring will be set to true.

After initialize(), restore() is then called with the data you previously returned with secure().

Coordinates of UI Elements[edit | edit source]

When creating UI elements, you have to give them a location where they'll be placed. The UIContainer:createLabel(...) and other UI creation functions expect local 2D coordinates, inside the window. But when you access coordinates of UI elements, they will return global 2D screen coordinates.

local rect = Rect(0, 0, 200, 40)
local button = window:createButton(rect, "Press Me", "onButtonPressed")

-- this will print the global render position of the button's rect. Also changes when the window is moved around
local glbl = button.rect
print (tostring(glbl.lower))
print (tostring(glbl.upper))

local lcl = button.localRect
print (tostring(lcl.lower))
print (tostring(lcl.upper))

If you want to use coordinates of UI elements, for example for formatting, you should keep that in mind. Use UIElement.localRect to access the local coordinates inside the window.

include() instead of require()[edit | edit source]

Avorion defines its own version of require(): include(). include() behaves the exact same way as require(), with one important difference: It can load mod file extensions. require() can only load a single file, and that can be problematic. It can be necessary for mods to modify files such as data/scripts/lib/utility.lua, which are being required by other lua files. But require() doesn't know about Avorion's mod structure, and so it cannot load additional files provided by mods, that are supposed to change how data/scripts/lib/utility.lua works.

include() looks for those files in addition to the vanilla file, and includes them into the loaded file.

You can read more about mod loading with include, and why you should use it, here.

Note: require() is still around and wasn't altered at all.

Restricted Access to local File System[edit | edit source]

In Avorion, client mods only have restricted access to the file system, for security reasons. So with io functions, you cannot modify files outside of %AppData%/Avorion (~/.avorion/ on Unix).

-- Not working: 
local file, err = io.open("/home/user/not_working_example.txt", "w")

-- Working:
local file, err = io.open("./moddata/working_example.txt", "w")  
if file==nil then
    print("Couldn't open file: "..err)
else
    file:write("example text")
    file:close()
end

See Also[edit | edit source]