rewrap, fix a code syntax error

develop
Myk 2022-09-14 10:56:58 -07:00 committed by GitHub
parent 51f5349baf
commit 7079fe7ea0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 114 additions and 90 deletions

@ -18,21 +18,22 @@ great primer at `lua.org <https://www.lua.org/pil/contents.html>`__.
Why not just mod the raws? Why not just mod the raws?
-------------------------- --------------------------
It depends on what you want to do. Some mods *are* better to do in just the raws. It depends on what you want to do. Some mods *are* better to do in just the
You don't need DFHack to add a new race or modify attributes, for example. However, raws. You don't need DFHack to add a new race or modify attributes, for example.
DFHack scripts can do many things that you just can't do in the raws, like make a However, DFHack scripts can do many things that you just can't do in the raws,
creature that trails smoke. Some things *could* be done in the raws, but writing a like make a creature that trails smoke. Some things *could* be done in the raws,
script is less hacky, easier to maintain, easier to extend, and is not prone to but writing a script is less hacky, easier to maintain, easier to extend, and is
side-effects. A great example is adding a syndrome when a reaction is performed. not prone to side-effects. A great example is adding a syndrome when a reaction
If done in the raws, you have to create an exploding boulder to effect the syndrome. is performed. If done in the raws, you have to create an exploding boulder to
DFHack scripts can add the syndrome directly and with much more flexibility. In the apply the syndrome. DFHack scripts can add the syndrome directly and with much
end, complex mods will likely require a mix of raw modding and DFHack scripting. more flexibility. In the end, complex mods will likely require a mix of raw
modding and DFHack scripting.
A mod-maker's development environment A mod-maker's development environment
------------------------------------- -------------------------------------
While you're writing your mod, you need a place to store your in-development scripts While you're writing your mod, you need a place to store your in-development
that will: scripts that will:
- be directly runnable by DFHack - be directly runnable by DFHack
- not get lost when you upgrade DFHack - not get lost when you upgrade DFHack
@ -42,17 +43,19 @@ installation (let's call it "/path/to/own-scripts") and do all your script
development in there. development in there.
Inside your DF installation folder, there is a file named Inside your DF installation folder, there is a file named
:file:`dfhack-config/script-paths.txt`. If you add a line like this to that file:: :file:`dfhack-config/script-paths.txt`. If you add a line like this to that
file::
+/path/to/own-scripts +/path/to/own-scripts
Then that directory will be searched when you run DFHack commands from inside the Then that directory will be searched when you run DFHack commands from inside
game. The ``+`` at the front of the path means to search that directory first, the game. The ``+`` at the front of the path means to search that directory
before any other script directory (like :file:`hack/scripts` or first, before any other script directory (like :file:`hack/scripts` or
:file:`raw/scripts`). That way, your latest changes will always be used instead of :file:`raw/scripts`). That way, your latest changes will always be used instead
older copies that you may have installed in a DF directory. of older copies that you may have installed in a DF directory.
For scripts with the same name, the `order of precedence <script-paths>` will be: For scripts with the same name, the `order of precedence <script-paths>` will
be:
1. ``own-scripts/`` 1. ``own-scripts/``
2. ``data/save/*/raw/scripts/`` 2. ``data/save/*/raw/scripts/``
@ -88,9 +91,9 @@ If ``unit`` is ``nil``, we don't want the script to run anymore::
end end
Now, the field ``sex`` in a unit is an integer, but each integer corresponds to Now, the field ``sex`` in a unit is an integer, but each integer corresponds to
a string value ("it", "she", or "he"). We get this value by indexing the bidirectional a string value ("it", "she", or "he"). We get this value by indexing the
map ``df.pronoun_type``. Indexing the other way, incidentally, with one of the strings, bidirectional map ``df.pronoun_type``. Indexing the other way, incidentally,
will yield its corresponding number. So:: with one of the strings, will yield its corresponding number. So::
local pronounTypeString = df.pronoun_type[unit.sex] local pronounTypeString = df.pronoun_type[unit.sex]
print(pronounTypeString) print(pronounTypeString)
@ -105,12 +108,12 @@ So how could you have known about the field and type we just used? Well, there
are two main tools for discovering the various fields in the game's data are two main tools for discovering the various fields in the game's data
structures. The first is the ``df-structures`` structures. The first is the ``df-structures``
`repository <https://github.com/DFHack/df-structures>`__ that contains XML files `repository <https://github.com/DFHack/df-structures>`__ that contains XML files
describing the contents of the game's structures. These are complete, but difficult describing the contents of the game's structures. These are complete, but
to read (for a human). The second option is the `gui/gm-editor` script, an difficult to read (for a human). The second option is the `gui/gm-editor`
interactive data explorer. You can run the script while objects like units are script, an interactive data explorer. You can run the script while objects like
selected to view the data within them. You can also run ``gui/gm-editor scr`` to units are selected to view the data within them. You can also run
view the data for the current screen. Press :kbd:`?` while the script is active to ``gui/gm-editor scr`` to view the data for the current screen. Press :kbd:`?`
view help. while the script is active to view help.
Familiarising yourself with the many structs of the game will help with ideas Familiarising yourself with the many structs of the game will help with ideas
immensely, and you can always ask for help in the `right places <support>`. immensely, and you can always ask for help in the `right places <support>`.
@ -121,23 +124,23 @@ Detecting triggers
The common method for injecting new behaviour into the game is to define a The common method for injecting new behaviour into the game is to define a
callback function and get it called when something interesting happens. DFHack callback function and get it called when something interesting happens. DFHack
provides two libraries for this, ``repeat-util`` and `eventful <eventful-api>`. provides two libraries for this, ``repeat-util`` and `eventful <eventful-api>`.
``repeat-util`` is used to run a function once per a configurable number of frames ``repeat-util`` is used to run a function once per a configurable number of
(paused or unpaused), ticks (unpaused), in-game days, months, or years. If you frames (paused or unpaused), ticks (unpaused), in-game days, months, or years.
need to be aware the instant something happens, you'll need to run a check once a If you need to be aware the instant something happens, you'll need to run a
tick. Be careful not to do this gratuitiously, though, since running that often can check once a tick. Be careful not to do this gratuitiously, though, since
slow down the game! running that often can slow down the game!
``eventful``, on the other hand, is much more performance-friendly since it will ``eventful``, on the other hand, is much more performance-friendly since it will
only call your callback when a relevant event happens, like a reaction or job being only call your callback when a relevant event happens, like a reaction or job
completed or a projectile moving. being completed or a projectile moving.
To get something to run once per tick, we can call ``repeat-util.scheduleEvery()``. To get something to run once per tick, we can call
First, we load the module:: ``repeat-util.scheduleEvery()``. First, we load the module::
local repeatUtil = require('repeat-util') local repeatUtil = require('repeat-util')
Both ``repeat-util`` and ``eventful`` require keys for registered callbacks. Both ``repeat-util`` and ``eventful`` require keys for registered callbacks. You
You should use something unique, like your mod name:: should use something unique, like your mod name::
local modId = "callback-example-mod" local modId = "callback-example-mod"
@ -145,8 +148,8 @@ Then, we pass the key, amount of time units between function calls, what the
time units are, and finally the callback function itself:: time units are, and finally the callback function itself::
repeatUtil.scheduleEvery(modId, 1, "ticks", function() repeatUtil.scheduleEvery(modId, 1, "ticks", function()
-- Do something like iterating over all active units and check -- Do something like iterating over all active units and
-- for something interesting -- check for something interesting
for _, unit in ipairs(df.global.world.units.active) do for _, unit in ipairs(df.global.world.units.active) do
... ...
end end
@ -162,17 +165,19 @@ the event occurs. So, for example, to print the position of a moving (item)
projectile:: projectile::
eventful.onProjItemCheckMovement[modId] = function(projectile) eventful.onProjItemCheckMovement[modId] = function(projectile)
print(projectile.cur_pos.x, projectile.cur_pos.y, projectile.cur_pos.z) print(projectile.cur_pos.x, projectile.cur_pos.y,
projectile.cur_pos.z)
end end
Check out the `full list of supported events <eventful-api>` to see what else Check out the `full list of supported events <eventful-api>` to see what else
you can react to with ``eventful``. you can react to with ``eventful``.
Now, you may have noticed that you won't be able to register multiple callbacks Now, you may have noticed that you won't be able to register multiple callbacks
with a single key named after your mod. You can, of course, call all the functions with a single key named after your mod. You can, of course, call all the
you want from a single registed callback. Alternately, you can create multiple functions you want from a single registed callback. Alternately, you can create
callbacks using different keys, using your mod ID as a key name prefix. If you do multiple callbacks using different keys, using your mod ID as a key name prefix.
register multiple callbacks, though, there are no guarantees about the call order. If you do register multiple callbacks, though, there are no guarantees about the
call order.
Custom raw tokens Custom raw tokens
----------------- -----------------
@ -229,8 +234,9 @@ First, require the modules we are going to use::
Now, let's make a callback (we'll be defining the body of this function soon):: Now, let's make a callback (we'll be defining the body of this function soon)::
local modId = "siege-crossbow-mod" local modId = "siege-crossbow-mod"
eventful.onReactionComplete[modId] = function(reaction, reactionProduct, eventful.onReactionComplete[modId] = function(reaction,
unit, inputItems, inputReagents, outputItems) reactionProduct, unit, inputItems, inputReagents,
outputItems)
First, we check to see if it the reaction that just happened is relevant to this First, we check to see if it the reaction that just happened is relevant to this
callback:: callback::
@ -279,9 +285,11 @@ Let's also make some code to modify the fire rate of our siege crossbow::
return return
end end
local multiplier = tonumber(customRawTokens.getToken(weapon.subtype, "SIEGE_CROSSBOW_MOD_FIRE_RATE_MULTIPLIER")) or 1 local multiplier = tonumber(customRawTokens.getToken(
firer.counters.think_counter = math.floor(firer.counters.think_counter * weapon.subtype,
multiplier) "SIEGE_CROSSBOW_MOD_FIRE_RATE_MULTIPLIER")) or 1
firer.counters.think_counter = math.floor(
firer.counters.think_counter * multiplier)
end end
Now, let's see how we could make some "pegasus boots". First, let's define the Now, let's see how we could make some "pegasus boots". First, let's define the
@ -301,26 +309,29 @@ item in the raws::
[LEATHER] [LEATHER]
[HARD] [HARD]
[PEGASUS_BOOTS_MOD_MOVEMENT_TIMER_REDUCTION_PER_TICK:5] custom raw token [PEGASUS_BOOTS_MOD_MOVEMENT_TIMER_REDUCTION_PER_TICK:5] custom raw token
(you don't have to comment the custom token every time, but it does clarify what it is) (you don't have to comment the custom token every time,
but it does clarify what it is)
Then, let's make a ``repeat-util`` callback for once a tick:: Then, let's make a ``repeat-util`` callback for once a tick::
repeatUtil.scheduleEvery(modId, 1, "ticks", function() repeatUtil.scheduleEvery(modId, 1, "ticks", function()
Let's iterate over every active unit, and for every unit, initialise a variable Let's iterate over every active unit, and for every unit, iterate over their
for how much we are going to take from their movement timer and iterate over all worn items to calculate how much we are going to take from their movement
their worn items: :: timer::
for _, unit in ipairs(df.global.world.units.active) do for _, unit in ipairs(df.global.world.units.active) do
local amount = 0 local amount = 0
for _, entry in ipairs(unit.inventory) do for _, entry in ipairs(unit.inventory) do
if entry.mode == df.unit_inventory_item.T_mode.Worn then
Now, we will add up the effect of all speed-increasing gear and apply it:: local reduction = customRawTokens.getToken(
entry.item,
if entry.mode == df.unit_inventory_item.T_mode.Worn then 'PEGASUS_BOOTS_MOD_MOVEMENT_TIMER_REDUCTION_PER_TICK')
amount = amount + tonumber((customRawTokens.getToken(entry.item, "PEGASUS_BOOTS_MOD_MOVEMENT_TIMER_REDUCTION_PER_TICK")) or 0) amount = amount + (tonumber(reduction) or 0)
end
end end
end end
-- Subtract amount from movement timer if currently moving -- Subtract amount from movement timer if currently moving
dfhack.units.addMoveTimer(-amount) dfhack.units.addMoveTimer(-amount)
@ -328,11 +339,11 @@ The structure of a full mod
--------------------------- ---------------------------
Create a folder for mod projects somewhere outside your Dwarf Fortress Create a folder for mod projects somewhere outside your Dwarf Fortress
installation directory (e.g. ``/path/to/mymods``) and use your mod IDs as the names installation directory (e.g. ``/path/to/mymods``) and use your mod IDs as the
for the mod folders within it. In the example below, we'll use a mod ID of names for the mod folders within it. In the example below, we'll use a mod ID of
``example-mod``. I'm sure your mods will have more creative names! The ``example-mod`` ``example-mod``. I'm sure your mods will have more creative names! The
mod will be developed in the ``/path/to/mymods/example-mod`` directory and has a basic ``example-mod`` mod will be developed in the ``/path/to/mymods/example-mod``
structure that looks like this:: directory and has a basic structure that looks like this::
raw/init.d/example-mod.lua raw/init.d/example-mod.lua
raw/objects/... raw/objects/...
@ -342,21 +353,23 @@ structure that looks like this::
Let's go through that line by line. Let's go through that line by line.
* You'll need a short script in ``raw/init.d/`` to initialise your mod when a save is * You'll need a short (one-line) script in ``raw/init.d/`` to initialise your
loaded. mod when a save is loaded.
* Modifications to the game raws (potentially with custom raw tokens) go in * Modifications to the game raws (potentially with custom raw tokens) go in
``raw/objects/``. ``raw/objects/``.
* A control script in ``raw/scripts/`` that handles enabling and disabling your mod. * A control script in ``raw/scripts/`` that handles enabling and disabling your
* A subfolder for your mod under ``raw/scripts/`` will contain all the internal scripts mod.
used by your mod. * A subfolder for your mod under ``raw/scripts/`` will contain all the internal
scripts and/or modules used by your mod.
It is a good idea to use a version control system to organize changes to your mod code. It is a good idea to use a version control system to organize changes to your
You can create a separate Git repository for each of your mods. The ``README.md`` file mod code. You can create a separate Git repository for each of your mods. The
will be your mod help text when people browse to your online repository. ``README.md`` file will be your mod help text when people browse to your online
repository.
Unless you want to install your ``raw`` folder into your DF game folder every time you Unless you want to install your ``raw`` folder into your DF game folder every
make a change to your scripts, you should add your development scripts directory to your time you make a change to your scripts, you should add your development scripts
script paths in ``dfhack-config/script-paths.txt``:: directory to your script paths in ``dfhack-config/script-paths.txt``::
+/path/to/mymods/example-mod/raw/scripts +/path/to/mymods/example-mod/raw/scripts
@ -364,8 +377,9 @@ Ok, you're all set up! Now, let's take a look at an example
``raw/scripts/example-mod.lua`` file:: ``raw/scripts/example-mod.lua`` file::
-- main setup and teardown for example-mod -- main setup and teardown for example-mod
-- this next line indicates that the script supports the "enable" API so you can start -- this next line indicates that the script supports the "enable"
-- it by running "enable example-mod" and stop it by running "disable example-mod" -- API so you can start it by running "enable example-mod" and stop
-- it by running "disable example-mod"
--@ enable = true --@ enable = true
local usage = [[ local usage = [[
@ -378,7 +392,8 @@ Ok, you're all set up! Now, let's take a look at an example
local repeatUtil = require('repeat-util') local repeatUtil = require('repeat-util')
local eventful = require('plugins.eventful') local eventful = require('plugins.eventful')
-- you can reference global values or functions declared in any of your internal scripts -- you can reference global values or functions declared in any of
-- your internal scripts
local moduleA = reqscript('example-mod/module-a') local moduleA = reqscript('example-mod/module-a')
local moduleB = reqscript('example-mod/module-b') local moduleB = reqscript('example-mod/module-b')
local moduleC = reqscript('example-mod/module-c') local moduleC = reqscript('example-mod/module-c')
@ -390,7 +405,8 @@ Ok, you're all set up! Now, let's take a look at an example
if not dfhack_flags.enable then if not dfhack_flags.enable then
print(usage) print(usage)
print() print()
print(('Example mod is currently '):format(enabled and 'enabled' or 'disabled')) print(('Example mod is currently '):format(
enabled and 'enabled' or 'disabled'))
return return
end end
@ -400,13 +416,19 @@ Ok, you're all set up! Now, let's take a look at an example
moduleB.onLoad() moduleB.onLoad()
-- register your callbacks -- register your callbacks
repeatUtil.scheduleEvery(modId .. ' every tick', 1, 'ticks', moduleA.every1Tick) repeatUtil.scheduleEvery(modId .. ' every tick', 1, 'ticks',
repeatUtil.scheduleEvery(modId .. ' 100 frames', 1, 'frames', moduleD.every100Frames) moduleA.every1Tick)
repeatUtil.scheduleEvery(modId .. ' 100 frames', 1, 'frames',
eventful.onReactionComplete[modId] = function(reaction, reaction_product, unit, input_items, input_reagents, output_items) moduleD.every100Frames)
eventful.onReactionComplete[modId] = function(reaction,
reaction_product, unit, input_items, input_reagents,
output_items)
-- pass the event's parameters to the listeners -- pass the event's parameters to the listeners
moduleB.onReactionComplete(reaction, reaction_product, unit, input_items, input_reagents, output_items) moduleB.onReactionComplete(reaction, reaction_product,
moduleC.onReactionComplete(reaction, reaction_product, unit, input_items, input_reagents, output_items) unit, input_items, input_reagents, output_items)
moduleC.onReactionComplete(reaction, reaction_product,
unit, input_items, input_reagents, output_items)
end end
eventful.onProjItemCheckMovement[modId] = moduleD.onProjItemCheckMovement eventful.onProjItemCheckMovement[modId] = moduleD.onProjItemCheckMovement
@ -444,8 +466,10 @@ Inside ``raw/scripts/example-mod/module-a.lua`` you could have code like this::
-- do initialization here -- do initialization here
end end
local function usedByOnTick(unit) -- local variables are not exported -- this is an internal function: local functions/variables
-- this is an internal function: local functions/variables are not exported -- are not exported
local function usedByOnTick(unit)
-- ...
end end
function onTick() -- exported function onTick() -- exported
@ -454,6 +478,6 @@ Inside ``raw/scripts/example-mod/module-a.lua`` you could have code like this::
end end
end end
The `reqscript` function reloads scripts that have changed, so you can modify your The `reqscript` function reloads scripts that have changed, so you can modify
scripts while DF is running and just disable/enable your mod to load the changes into your scripts while DF is running and just disable/enable your mod to load the
your ongoing game! changes into your ongoing game!