From 0ae8a420800540692916395fbc8b629e6f9a8179 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Mon, 20 Jun 2022 19:38:23 +0100 Subject: [PATCH 001/121] Unfinished modding guide --- docs/changelog.txt | 5 +++ docs/guides/modding-guide.rst | 74 +++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 docs/guides/modding-guide.rst diff --git a/docs/changelog.txt b/docs/changelog.txt index e91a427a9..e944b7799 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -69,6 +69,11 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `quickfort-alias-guide`: new aliases: ``forbidthread``, ``permitthread``, ``forbidadamantinethread``, ``permitadamantinethread``, ``forbidcloth``, ``permitcloth``, ``forbidadamantinecloth``, and ``permitadamantinecloth`` give you more control how adamantine-derived items are stored - `quickfort`: `Dreamfort ` blueprint set improvements: automatically create tavern, library, and temple locations (restricted to residents only by default), automatically associate the rented rooms with the tavern - `quickfort`: `Dreamfort ` blueprint set improvements: new design for the services level, including were-bitten hospital recovery rooms and an appropriately-themed interrogation room next to the jail! Also fits better in a 1x1 embark for minimalist players. +- `quickfort`: `Dreamfort ` blueprint set improvements: new design for the services level, including a werebeast-proof hospital recovery rooms and an appropriately-themed interrogation room next to the jail! Also fits better in a 1x1 embark for minimalist players. + +## Documentation +- Added modding guide +- Added anchor for DF data structure wrapper in Lua API ## API - ``word_wrap``: argument ``bool collapse_whitespace`` converted to enum ``word_wrap_whitespace_mode mode``, with valid modes ``WSMODE_KEEP_ALL``, ``WSMODE_COLLAPSE_ALL``, and ``WSMODE_TRIM_LEADING``. diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst new file mode 100644 index 000000000..d2c453829 --- /dev/null +++ b/docs/guides/modding-guide.rst @@ -0,0 +1,74 @@ +DFHack modding guide +==================== + +Preface +------- + +Why not just use raw modding? + +It's much less hacky for many things, easier to maintain, and easier to extend. Of course if you're adding a new creature or whatever do use raw modding, but for things like adding syndromes when certain actions are performed, absolutely try out DFHack. Many things will require a mix of raw modding and DFHack. + +What kind of things can we make? + +Lots of things, and the list grows as more and more hooks and tools are developed within DFHack. You can modify behaviours by cleverly watching and modifying certain areas of the game with functions that run on every tick or by using hooks. Familiarising yourself with the many structs oof the game will help with ideas immensely, and you can always ask for help in the right places (e.g. DFHack's Discord). + +You will need to know Lua for the scripting guides herein. + +A script-maker's environment +---------------------------- + +A script is run by writing its path and name from a script path folder without the file extension. E.g. gui/gm-editor for hack/scripts/gui/gm-editor.lua. You can make all your scripts in hack/scripts/, but this is not recommended as it makes things much harder to maintain each update. It's recommended to make a folder with a name like "own-scripts" and add it to dfhack-config/script-paths.txt. You should also make a folder for external installed scripts from the internet that are not in hack/scripts/. + +The structure of the game +------------------------- + +"The game" is in the global variable `df `. The game's memory can be found in ``df.global``, containing things like the list of all items, whether to reindex pathfinding, et cetera. Also relevant to us in ``df`` are the various types found in the game, e.g. ``df.pronoun_type`` which we will be using. + +A script that prints data from a unit +------------------------------------- + +So! It's time to write your first script. We are going to make a script that will get the pronoun type of the currently selected unit (there are many contexts where the function that gets the currently selected unit works). + +First line, we get the unit.:: + + local unit = dfhack.gui.getSelectedUnit() + +If no unit is selected, an error message will be printed (which can be silenced by passing ``true`` to ``getSelectedUnit``) and ``unit`` will be ``nil``. + +If ``unit`` is ``nil``, we don't want the script to run anymore.:: + + if not unit then + return + end + +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 map ``df.pronoun_type`` with an integer from the unit. Indexing the other way, with one of the strings, will yield its corresponding number. So::: + + local pronounTypeString = df.pronoun_type[unit.sex] + print(pronounTypeString) + +Simple. + +Getting used to gm-editor and DFStructs exploration +--------------------------------------------------- + +So how could you have known about the field and type we just used? + +A script that prints more complex data from a unit +-------------------------------------------------- + +s + +Something that uses eventful and/or repeat-util +----------------------------------------------- + +s + +Setting up an environment for a more advanced modular mod +--------------------------------------------------------- + +s + +A whole mod with multiple hooks and multiple functions that happen on tick +-------------------------------------------------------------------------- + +s From 063e4897a84b8579bfcfd022f02602dff26ddb62 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Mon, 20 Jun 2022 20:31:19 +0100 Subject: [PATCH 002/121] Forgot to stage a change --- docs/Lua API.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 55a217757..f2f252c4b 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -30,6 +30,7 @@ implemented by Lua files located in :file:`hack/lua/*` :local: :depth: 2 +.. _lua-df: ========================= DF data structure wrapper From a880a2b92d5cb8e7c3f15d7c80315cee736b24c6 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Tue, 21 Jun 2022 12:48:28 +0100 Subject: [PATCH 003/121] Move changelog entries to proper place --- docs/changelog.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index e944b7799..a07335715 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -42,6 +42,8 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Misc Improvements ## Documentation +- Added modding guide +- Added anchor for DF data structure wrapper in Lua API ## API @@ -72,8 +74,6 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `quickfort`: `Dreamfort ` blueprint set improvements: new design for the services level, including a werebeast-proof hospital recovery rooms and an appropriately-themed interrogation room next to the jail! Also fits better in a 1x1 embark for minimalist players. ## Documentation -- Added modding guide -- Added anchor for DF data structure wrapper in Lua API ## API - ``word_wrap``: argument ``bool collapse_whitespace`` converted to enum ``word_wrap_whitespace_mode mode``, with valid modes ``WSMODE_KEEP_ALL``, ``WSMODE_COLLAPSE_ALL``, and ``WSMODE_TRIM_LEADING``. From ccee6ba48700c16af900e7ff0a5cec54e1cfa0c4 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Tue, 21 Jun 2022 12:53:59 +0100 Subject: [PATCH 004/121] Added link to modding guide from changelog --- docs/changelog.txt | 2 +- docs/guides/modding-guide.rst | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index a07335715..2118a5380 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -42,7 +42,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Misc Improvements ## Documentation -- Added modding guide +- Added `modding guide ` - Added anchor for DF data structure wrapper in Lua API ## API diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index d2c453829..5d55ca44e 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -1,3 +1,5 @@ +.. _modding-guide: + DFHack modding guide ==================== From 4c2a533de0960a8fc27d0105c56a550b6ffb0fdb Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Tue, 21 Jun 2022 12:54:28 +0100 Subject: [PATCH 005/121] Remove "added anchor" from changelog --- docs/changelog.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 2118a5380..a54957796 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -43,7 +43,6 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Documentation - Added `modding guide ` -- Added anchor for DF data structure wrapper in Lua API ## API From 88a9755d785e2ecd751c7e0ee1711aadf188eb85 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Tue, 21 Jun 2022 12:54:45 +0100 Subject: [PATCH 006/121] Update docs/guides/modding-guide.rst Co-authored-by: Myk --- docs/guides/modding-guide.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index 5d55ca44e..58f32fa28 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -26,7 +26,7 @@ The structure of the game "The game" is in the global variable `df `. The game's memory can be found in ``df.global``, containing things like the list of all items, whether to reindex pathfinding, et cetera. Also relevant to us in ``df`` are the various types found in the game, e.g. ``df.pronoun_type`` which we will be using. -A script that prints data from a unit +Your first script ------------------------------------- So! It's time to write your first script. We are going to make a script that will get the pronoun type of the currently selected unit (there are many contexts where the function that gets the currently selected unit works). From 92ab6b10529509b804bae8a4d86397da1cade856 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Tue, 21 Jun 2022 12:58:29 +0100 Subject: [PATCH 007/121] Update docs/guides/modding-guide.rst Co-authored-by: Myk --- docs/guides/modding-guide.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index 58f32fa28..dfd143e08 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -14,7 +14,7 @@ What kind of things can we make? Lots of things, and the list grows as more and more hooks and tools are developed within DFHack. You can modify behaviours by cleverly watching and modifying certain areas of the game with functions that run on every tick or by using hooks. Familiarising yourself with the many structs oof the game will help with ideas immensely, and you can always ask for help in the right places (e.g. DFHack's Discord). -You will need to know Lua for the scripting guides herein. +DFHack scripts are written in Lua. If you don't already know Lua, there's a great primer at . A script-maker's environment ---------------------------- From 52161df42863a14c18c43a95a90495eaf7a2811d Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Tue, 21 Jun 2022 12:58:36 +0100 Subject: [PATCH 008/121] Update docs/guides/modding-guide.rst Co-authored-by: Myk --- docs/guides/modding-guide.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index dfd143e08..d4514156e 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -16,7 +16,7 @@ Lots of things, and the list grows as more and more hooks and tools are develope DFHack scripts are written in Lua. If you don't already know Lua, there's a great primer at . -A script-maker's environment +A mod-maker's development environment ---------------------------- A script is run by writing its path and name from a script path folder without the file extension. E.g. gui/gm-editor for hack/scripts/gui/gm-editor.lua. You can make all your scripts in hack/scripts/, but this is not recommended as it makes things much harder to maintain each update. It's recommended to make a folder with a name like "own-scripts" and add it to dfhack-config/script-paths.txt. You should also make a folder for external installed scripts from the internet that are not in hack/scripts/. From 6f2998d7e5514f4a7bbd922f33dae4efdc33c901 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Tue, 21 Jun 2022 12:58:40 +0100 Subject: [PATCH 009/121] Update docs/guides/modding-guide.rst Co-authored-by: Myk --- docs/guides/modding-guide.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index d4514156e..c8f13baf7 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -10,7 +10,7 @@ Why not just use raw modding? It's much less hacky for many things, easier to maintain, and easier to extend. Of course if you're adding a new creature or whatever do use raw modding, but for things like adding syndromes when certain actions are performed, absolutely try out DFHack. Many things will require a mix of raw modding and DFHack. -What kind of things can we make? +What kind of mods can we make? Lots of things, and the list grows as more and more hooks and tools are developed within DFHack. You can modify behaviours by cleverly watching and modifying certain areas of the game with functions that run on every tick or by using hooks. Familiarising yourself with the many structs oof the game will help with ideas immensely, and you can always ask for help in the right places (e.g. DFHack's Discord). From 41526f376d9bc9bcb68b6526eb4f31e72bb6003e Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Tue, 21 Jun 2022 13:02:10 +0100 Subject: [PATCH 010/121] Fix wrong - length --- docs/guides/modding-guide.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index c8f13baf7..e60bc2650 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -17,7 +17,7 @@ Lots of things, and the list grows as more and more hooks and tools are develope DFHack scripts are written in Lua. If you don't already know Lua, there's a great primer at . A mod-maker's development environment ----------------------------- +------------------------------------- A script is run by writing its path and name from a script path folder without the file extension. E.g. gui/gm-editor for hack/scripts/gui/gm-editor.lua. You can make all your scripts in hack/scripts/, but this is not recommended as it makes things much harder to maintain each update. It's recommended to make a folder with a name like "own-scripts" and add it to dfhack-config/script-paths.txt. You should also make a folder for external installed scripts from the internet that are not in hack/scripts/. @@ -27,7 +27,7 @@ The structure of the game "The game" is in the global variable `df `. The game's memory can be found in ``df.global``, containing things like the list of all items, whether to reindex pathfinding, et cetera. Also relevant to us in ``df`` are the various types found in the game, e.g. ``df.pronoun_type`` which we will be using. Your first script -------------------------------------- +----------------- So! It's time to write your first script. We are going to make a script that will get the pronoun type of the currently selected unit (there are many contexts where the function that gets the currently selected unit works). From a9ab415ac7f5443a7129db461055125a7d808f6d Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Tue, 21 Jun 2022 15:20:49 +0100 Subject: [PATCH 011/121] Clean merge heckery and clean up label reference --- docs/changelog.txt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index a54957796..797a79f3a 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -42,7 +42,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Misc Improvements ## Documentation -- Added `modding guide ` +- Added `modding-guide` ## API @@ -70,9 +70,6 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `quickfort-alias-guide`: new aliases: ``forbidthread``, ``permitthread``, ``forbidadamantinethread``, ``permitadamantinethread``, ``forbidcloth``, ``permitcloth``, ``forbidadamantinecloth``, and ``permitadamantinecloth`` give you more control how adamantine-derived items are stored - `quickfort`: `Dreamfort ` blueprint set improvements: automatically create tavern, library, and temple locations (restricted to residents only by default), automatically associate the rented rooms with the tavern - `quickfort`: `Dreamfort ` blueprint set improvements: new design for the services level, including were-bitten hospital recovery rooms and an appropriately-themed interrogation room next to the jail! Also fits better in a 1x1 embark for minimalist players. -- `quickfort`: `Dreamfort ` blueprint set improvements: new design for the services level, including a werebeast-proof hospital recovery rooms and an appropriately-themed interrogation room next to the jail! Also fits better in a 1x1 embark for minimalist players. - -## Documentation ## API - ``word_wrap``: argument ``bool collapse_whitespace`` converted to enum ``word_wrap_whitespace_mode mode``, with valid modes ``WSMODE_KEEP_ALL``, ``WSMODE_COLLAPSE_ALL``, and ``WSMODE_TRIM_LEADING``. From ac864204c2258151b05bbe019c006c26e2c14e6b Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Tue, 21 Jun 2022 21:07:35 +0100 Subject: [PATCH 012/121] More work on guide, added to existing sections and revised sections list --- docs/guides/modding-guide.rst | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index e60bc2650..f124ba121 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -8,18 +8,24 @@ Preface Why not just use raw modding? -It's much less hacky for many things, easier to maintain, and easier to extend. Of course if you're adding a new creature or whatever do use raw modding, but for things like adding syndromes when certain actions are performed, absolutely try out DFHack. Many things will require a mix of raw modding and DFHack. +For many things it's either completely and only (sensibly) doable in raws or completely and only doable with DFHack. For mods where DFHack is an alternative and not the only option, it's much less hacky, easier to maintain, and easier to extend, and is not prone to side-effects. A great example is adding a syndrome when a reaction is performed requiring an exploding boulder in raws but having dedicated tools for it if you use DFHack. Many things will require a mix of raw modding and DFHack. What kind of mods can we make? -Lots of things, and the list grows as more and more hooks and tools are developed within DFHack. You can modify behaviours by cleverly watching and modifying certain areas of the game with functions that run on every tick or by using hooks. Familiarising yourself with the many structs oof the game will help with ideas immensely, and you can always ask for help in the right places (e.g. DFHack's Discord). +Lots of things, and the list grows as more and more hooks and tools are developed within DFHack. You can modify behaviours by cleverly watching and modifying certain areas of the game with functions that run on every tick or by using hooks. 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 (e.g. DFHack's Discord). -DFHack scripts are written in Lua. If you don't already know Lua, there's a great primer at . +Examples of things we have mod tools to allow are directly changing skills, spawning liquids, adding new powered buildings, creating items/trees/units based on various conditions (in reactions or similar), running code when certain projectiles hit or move, and much else besides. + +DFHack scripts are written in Lua. If you don't already know Lua, there's a great primer at https://www.lua.org/pil/1.html. A mod-maker's development environment ------------------------------------- -A script is run by writing its path and name from a script path folder without the file extension. E.g. gui/gm-editor for hack/scripts/gui/gm-editor.lua. You can make all your scripts in hack/scripts/, but this is not recommended as it makes things much harder to maintain each update. It's recommended to make a folder with a name like "own-scripts" and add it to dfhack-config/script-paths.txt. You should also make a folder for external installed scripts from the internet that are not in hack/scripts/. +Scripts can be run from a world's ``raw/scripts/`` directory, and (configurably) are run by default from ``hack/scripts/``. Scripts in ``raw/init.d/`` are automatically run on world load. Scripts within the raws are a component for more advanced mods. + +A script is run by writing its path and name from a script path folder without the file extension into a DFHack command prompt (in-game or the external one). E.g. ``gui/gm-editor`` for ``hack/scripts/gui/gm-editor.lua``. + +You can make all your scripts in ``hack/scripts/``, but this is not recommended as it makes things much harder to maintain each update. It's recommended to make a folder with a name like "own-scripts" and add it to ``dfhack-config/script-paths.txt``. You should also make a folder for external installed scripts from the internet that are not in ``hack/scripts/``. You can prepend your script paths entries with a ``+`` so that they take precedence over other folders. The structure of the game ------------------------- @@ -48,20 +54,17 @@ Now, the field ``sex`` in a unit is an integer, but each integer corresponds to local pronounTypeString = df.pronoun_type[unit.sex] print(pronounTypeString) -Simple. +Simple. Save this as a Lua file in your own scripts directory and run it as shown before when focused on a unit one way or another. Getting used to gm-editor and DFStructs exploration --------------------------------------------------- So how could you have known about the field and type we just used? -A script that prints more complex data from a unit --------------------------------------------------- - s -Something that uses eventful and/or repeat-util ------------------------------------------------ +Detecting triggers +------------------ s @@ -70,7 +73,7 @@ Setting up an environment for a more advanced modular mod s -A whole mod with multiple hooks and multiple functions that happen on tick --------------------------------------------------------------------------- +Your first whole mod +-------------------- s From f812e09ae1470b26d3375337bc98de90f27692b4 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Tue, 21 Jun 2022 21:35:07 +0100 Subject: [PATCH 013/121] Update modding guide with an extra paragraph and syntax fixes --- docs/guides/modding-guide.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index f124ba121..d3bf384c8 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -27,6 +27,8 @@ A script is run by writing its path and name from a script path folder without t You can make all your scripts in ``hack/scripts/``, but this is not recommended as it makes things much harder to maintain each update. It's recommended to make a folder with a name like "own-scripts" and add it to ``dfhack-config/script-paths.txt``. You should also make a folder for external installed scripts from the internet that are not in ``hack/scripts/``. You can prepend your script paths entries with a ``+`` so that they take precedence over other folders. +If your mod is installed into ``raw/scripts/`` be aware that the copies of the scripts in ``data/save/*/raw/`` are checked first and will run instead of any changes you make to an in-development copy outside of a raw folder. + The structure of the game ------------------------- @@ -37,19 +39,19 @@ Your first script So! It's time to write your first script. We are going to make a script that will get the pronoun type of the currently selected unit (there are many contexts where the function that gets the currently selected unit works). -First line, we get the unit.:: +First line, we get the unit. :: local unit = dfhack.gui.getSelectedUnit() If no unit is selected, an error message will be printed (which can be silenced by passing ``true`` to ``getSelectedUnit``) and ``unit`` will be ``nil``. -If ``unit`` is ``nil``, we don't want the script to run anymore.:: +If ``unit`` is ``nil``, we don't want the script to run anymore. :: if not unit then return end -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 map ``df.pronoun_type`` with an integer from the unit. Indexing the other way, with one of the strings, will yield its corresponding number. So::: +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 map ``df.pronoun_type`` with an integer from the unit. Indexing the other way, with one of the strings, will yield its corresponding number. So: :: local pronounTypeString = df.pronoun_type[unit.sex] print(pronounTypeString) From c026bd6dcb659cef31e2481bb38a1d01aaec22c5 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Fri, 1 Jul 2022 12:04:18 +0100 Subject: [PATCH 014/121] Split sections a bit --- docs/guides/modding-guide.rst | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index d3bf384c8..cb37de575 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -3,20 +3,17 @@ DFHack modding guide ==================== -Preface -------- +What is a mod/script? +--------------------- -Why not just use raw modding? - -For many things it's either completely and only (sensibly) doable in raws or completely and only doable with DFHack. For mods where DFHack is an alternative and not the only option, it's much less hacky, easier to maintain, and easier to extend, and is not prone to side-effects. A great example is adding a syndrome when a reaction is performed requiring an exploding boulder in raws but having dedicated tools for it if you use DFHack. Many things will require a mix of raw modding and DFHack. - -What kind of mods can we make? +A script is a single file that can be run as a command in DFHack, like something that modifies or displays game data on request. A mod is something you install to get persistent behavioural changes in the game and/or add new content. DFHack mods contain and use scripts as well as often having a raw mod component. -Lots of things, and the list grows as more and more hooks and tools are developed within DFHack. You can modify behaviours by cleverly watching and modifying certain areas of the game with functions that run on every tick or by using hooks. 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 (e.g. DFHack's Discord). +DFHack scripts are written in Lua. If you don't already know Lua, there's a great primer at https://www.lua.org/pil/1.html. -Examples of things we have mod tools to allow are directly changing skills, spawning liquids, adding new powered buildings, creating items/trees/units based on various conditions (in reactions or similar), running code when certain projectiles hit or move, and much else besides. +Why not just use raw modding? +----------------------------- -DFHack scripts are written in Lua. If you don't already know Lua, there's a great primer at https://www.lua.org/pil/1.html. +For many things it's either completely and only (sensibly) doable in raws or completely and only doable with DFHack. For mods where DFHack is an alternative and not the only option, it's much less hacky, easier to maintain, and easier to extend, and is not prone to side-effects. A great example is adding a syndrome when a reaction is performed requiring an exploding boulder in raws but having dedicated tools for it if you use DFHack. Many things will require a mix of raw modding and DFHack. A mod-maker's development environment ------------------------------------- From 46d0f36d412eedf2bd02bbd7ac901cb29536f095 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Fri, 1 Jul 2022 19:10:26 +0100 Subject: [PATCH 015/121] Pad out Getting used to gm-editor and DFStructs exploration a bit --- docs/guides/modding-guide.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index cb37de575..c35e2a41a 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -58,9 +58,9 @@ Simple. Save this as a Lua file in your own scripts directory and run it as show Getting used to gm-editor and DFStructs exploration --------------------------------------------------- -So how could you have known about the field and type we just used? +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 structures. The first is the ``df-structures`` repository (at https://github.com/DFHack/df-structures) that contains XML files denoting the contents of the game's structures. The second is the script ``gui/gm-editor`` which is an interactive data explorer. You can run the script while objects like units are selected to view the data within them. You can also pass ``scr`` as an argument to the script to view the data for the current screen. Press ? while the script is active to view help. -s +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 (e.g. DFHack's Discord). Detecting triggers ------------------ From b86d16d64ceb76745179936771eea724c2476b12 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Sun, 3 Jul 2022 15:59:01 +0100 Subject: [PATCH 016/121] Some of Detecting Triggers --- docs/guides/modding-guide.rst | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index c35e2a41a..9bbdc9bfd 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -65,7 +65,30 @@ Familiarising yourself with the many structs of the game will help with ideas im Detecting triggers ------------------ -s +One main method for getting new behaviour into the game is callback functions. There are two main libraries for this, ``repeat-util`` and ``eventful``. ``repeat-util`` is used to run a function once per configurable number of frames (paused or unpaused), ticks (unpaused), in-game days, months, or years. For adding behaviour you will most often want something to run once a tick. ``eventful`` is used to get code to run (with special parameters!) when something happens in the game, like a reaction or job being completed or a projectile moving. + +To get something to run once per tick, we would want to call ``repeat-util``'s ``scheduleEvery`` function. + +First, we load the module: :: + + local repeatUtil = require("repeat-util") + +Both ``repeat-util`` and ``eventful`` require keys for registered callbacks. It's recommended to use something like a mod id. :: + + local modId = "my-test-mod" + +Then, we pass the key, amount of time units between function calls, what the time units are, and finally the callback function itself: + + repeatUtil.scheduleEvery(modId, 1, "ticks", function() + -- Do something like iterating over all units + for _, unit in ipairs(df.global.world.units.all) do + print(unit.id) + end + end) + +``eventful`` is slightly more involved: + +TODO Setting up an environment for a more advanced modular mod --------------------------------------------------------- From f8a8bf6e2993a2ac5a548e2406223a9757b154e6 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Sun, 3 Jul 2022 17:33:36 +0100 Subject: [PATCH 017/121] Some more --- docs/guides/modding-guide.rst | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index 9bbdc9bfd..c6ae8ae15 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -77,7 +77,7 @@ Both ``repeat-util`` and ``eventful`` require keys for registered callbacks. It' local modId = "my-test-mod" -Then, we pass the key, amount of time units between function calls, what the time units are, and finally the callback function itself: +Then, we pass the key, amount of time units between function calls, what the time units are, and finally the callback function itself: :: repeatUtil.scheduleEvery(modId, 1, "ticks", function() -- Do something like iterating over all units @@ -86,9 +86,12 @@ Then, we pass the key, amount of time units between function calls, what the tim end end) -``eventful`` is slightly more involved: +``eventful`` is slightly more involved. :: -TODO + local eventful = require("plugins.eventful") + -- blah + +Check the full list of Eventful events at https://docs.dfhack.org/en/stable/docs/Lua%20API.html#list-of-events. Setting up an environment for a more advanced modular mod --------------------------------------------------------- From 0237567c183f7a50305b2db47a2c0902a8eb3189 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Mon, 4 Jul 2022 17:11:21 +0100 Subject: [PATCH 018/121] More modding guide --- docs/guides/modding-guide.rst | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index c6ae8ae15..b3b5c515b 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -86,17 +86,24 @@ Then, we pass the key, amount of time units between function calls, what the tim end end) -``eventful`` is slightly more involved. :: +``eventful`` is slightly more involved. First get the module: :: local eventful = require("plugins.eventful") - -- blah -Check the full list of Eventful events at https://docs.dfhack.org/en/stable/docs/Lua%20API.html#list-of-events. +``eventful`` contains a table for each event which you populate with functions. Each function in the table is then called with the appropriate arguments when the event occurs. So, for example, to print the position of a moving (item) projectile: :: + + eventful.onProjItemCheckMovement[modId] = function(projectile) + print(projectile.cur_pos.x, projectile.cur_pos.y, projectile.cur_pos.z) + end + +Check the full list of events at https://docs.dfhack.org/en/stable/docs/Lua%20API.html#list-of-events. Setting up an environment for a more advanced modular mod --------------------------------------------------------- -s +Now, you may have noticed that you won't be able to run multiple functions on tick/as event callbacks with that ``modId`` idea alone. To solve that we can just define all the functions we want and call them from a single function. + +TODO Your first whole mod -------------------- From 15aae9cf1ef90c862d75c28e4d81d93764c59a6e Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Thu, 7 Jul 2022 19:23:10 +0100 Subject: [PATCH 019/121] More guide --- docs/guides/modding-guide.rst | 79 ++++++++++++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 5 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index b3b5c515b..085c53a1e 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -98,14 +98,83 @@ Then, we pass the key, amount of time units between function calls, what the tim Check the full list of events at https://docs.dfhack.org/en/stable/docs/Lua%20API.html#list-of-events. -Setting up an environment for a more advanced modular mod ---------------------------------------------------------- +Custom raw tokens +----------------- -Now, you may have noticed that you won't be able to run multiple functions on tick/as event callbacks with that ``modId`` idea alone. To solve that we can just define all the functions we want and call them from a single function. +In this section, we are going to use custom raw tokens applied to a reaction to transfer the material of a reagent to a product as a handle improvement (like on artifact buckets), and then we are going to see how you could make boots that make units go faster when worn. Both of these involve custom raw tokens. + +First, let's define a custom crossbow with its own custom reaction. The crossbow: :: + + [ITEM_WEAPON:ITEM_WEAPON_CROSSBOW_SIEGE] + [NAME:crossbow:crossbows] + [SIZE:600] + [SKILL:HAMMER] + [RANGED:CROSSBOW:BOLT] + [SHOOT_FORCE:4000] + [SHOOT_MAXVEL:800] + [TWO_HANDED:0] + [MINIMUM_SIZE:17500] + [MATERIAL_SIZE:4] + [ATTACK:BLUNT:10000:4000:bash:bashes:NO_SUB:1250] + [ATTACK_PREPARE_AND_RECOVER:3:3] + [FIRE_TIME:100] custom token (you'll see) + +The reaction to make it (you would add the reaction and not the weapon to an entity raw): :: + + [REACTION:MAKE_SIEGE_CROSSBOW] + [NAME:make siege crossbow] + [BUILDING:BOWYER:NONE] + [SKILL:BOWYER] + [REAGENT:mechanism 1:2:TRAPPARTS:NONE:NONE:NONE] + [REAGENT:bar:150:BAR:NONE:NONE:NONE] + [METAL_ITEM_MATERIAL] + [REAGENT:handle 1:1:BLOCKS:NONE:NONE:NONE] wooden handles + [ANY_PLANT_MATERIAL] + [REAGENT:handle 2:1:BLOCKS:NONE:NONE:NONE] + [ANY_PLANT_MATERIAL] + [TRANSFER_HANDLE_MATERIAL_TO_PRODUCT_IMPROVEMENT:1] another custom token + [PRODUCT:100:1:WEAPON:ITEM_WEAPON_CROSSBOW_SIEGE:GET_MATERIAL_FROM_REAGENT:bar:NONE] + +So, we are going to use the ``eventful`` module to make it so that (after the script is run) when this crossbow is crafted, it will have two handles, each with the material given by the block reagents. + +First, require the modules we are going to use. :: -TODO + local eventful = require("plugins.eventful") + local customRawTokens = require("custom-raw-tokens") + +Now, let's make a callback: :: + + local modId = "siege-crossbow" + eventful.onReactionComplete[modId] = function(reaction, reactionProduct, unit, inputItems, inputReagents, outputItems) + +First, we check to see if it the reaction that just happened is relevant to this callback: :: + + if not customRawTokens.getToken(reaction, "TRANSFER_HANDLE_MATERIAL_TO_PRODUCT_IMPROVEMENT") then return end + +Then, we get the product number listed. Next, for every reagent, if the reagent name starts with "handle" then we get the corresponding item, and... :: + + for i, reagent in ipairs(inputReagents) do + if reagent.code:sub(1, #"handle") == "handle" then + -- Found handle reagent + local item = inputItems[i] -- hopefully found handle item + +...We then add a handle improvement to the listed product within our loop. :: + + local new = df.itemimprovement_itemspecificst:new() + new.mat_type, new.mat_index = item.mat_type, item.mat_index + -- new.maker = outputItems[0].maker -- not a typical improvement + new.type = df.itemimprovement_specific_type.HANDLE + outputItems[productNumber - 1].improvements:insert("#", new) + -- break -- multiple handles, multiple "the handle is made from"s, so no break + +It's all a bit loose and hacky but it works, at least if you don't have multiple stacks filling up one reagent. + +TODO: fire rate +TODO: "running shoes" Your first whole mod -------------------- -s +Now, you may have noticed that you won't be able to run multiple functions on tick/as event callbacks with that ``modId`` idea alone. To solve that we can just define all the functions we want and call them from a single function. + +TODO From 25175b5c2811d5f1a2da512485b8e1679ad2cde5 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Sat, 9 Jul 2022 17:04:20 +0100 Subject: [PATCH 020/121] Fire rate code --- docs/guides/modding-guide.rst | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index 085c53a1e..15e96599d 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -117,7 +117,7 @@ First, let's define a custom crossbow with its own custom reaction. The crossbow [MATERIAL_SIZE:4] [ATTACK:BLUNT:10000:4000:bash:bashes:NO_SUB:1250] [ATTACK_PREPARE_AND_RECOVER:3:3] - [FIRE_TIME:100] custom token (you'll see) + [FIRE_RATE_MULTIPLIER:2] custom token (you'll see) The reaction to make it (you would add the reaction and not the weapon to an entity raw): :: @@ -169,7 +169,27 @@ Then, we get the product number listed. Next, for every reagent, if the reagent It's all a bit loose and hacky but it works, at least if you don't have multiple stacks filling up one reagent. -TODO: fire rate +Let's also make some code to modify the fire rate of the siege crossbow. :: + + eventful.onProjItemCheckMovement[modId] = function(projectile) + if projectile.distance_flown > 0 then -- don't repeat this + return + end + + local firer = projectile.firer + if not firer then + return + end + + local weapon = df.item.find(projectile.bow_id) + if not weapon then + return + end + + local multiplier = tonumber(customRawTokens.getToken(weapon.subtype, "FIRE_RATE_MULTIPLIER")) or 1 + firer.counters.think_counter = math.floor(firer.counters.think_counter * multiplier) + end + TODO: "running shoes" Your first whole mod From f670ef4b60a694f565745076df6d862cb8d6838e Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Mon, 11 Jul 2022 18:24:07 +0100 Subject: [PATCH 021/121] Add custom raw tokens link and add prefix to custom raw tokens in raws --- docs/guides/modding-guide.rst | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index 15e96599d..ea7ae9eb7 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -101,7 +101,7 @@ Check the full list of events at https://docs.dfhack.org/en/stable/docs/Lua%20AP Custom raw tokens ----------------- -In this section, we are going to use custom raw tokens applied to a reaction to transfer the material of a reagent to a product as a handle improvement (like on artifact buckets), and then we are going to see how you could make boots that make units go faster when worn. Both of these involve custom raw tokens. +In this section, we are going to use `custom raw tokens <_custom-raw-tokens>` applied to a reaction to transfer the material of a reagent to a product as a handle improvement (like on artifact buckets), and then we are going to see how you could make boots that make units go faster when worn. Both of these involve custom raw tokens. First, let's define a custom crossbow with its own custom reaction. The crossbow: :: @@ -117,7 +117,7 @@ First, let's define a custom crossbow with its own custom reaction. The crossbow [MATERIAL_SIZE:4] [ATTACK:BLUNT:10000:4000:bash:bashes:NO_SUB:1250] [ATTACK_PREPARE_AND_RECOVER:3:3] - [FIRE_RATE_MULTIPLIER:2] custom token (you'll see) + [TUTORIAL_MOD_FIRE_RATE_MULTIPLIER:2] custom token (you'll see) The reaction to make it (you would add the reaction and not the weapon to an entity raw): :: @@ -132,7 +132,7 @@ The reaction to make it (you would add the reaction and not the weapon to an ent [ANY_PLANT_MATERIAL] [REAGENT:handle 2:1:BLOCKS:NONE:NONE:NONE] [ANY_PLANT_MATERIAL] - [TRANSFER_HANDLE_MATERIAL_TO_PRODUCT_IMPROVEMENT:1] another custom token + [TUTORIAL_MOD_TRANSFER_HANDLE_MATERIAL_TO_PRODUCT_IMPROVEMENT:1] another custom token [PRODUCT:100:1:WEAPON:ITEM_WEAPON_CROSSBOW_SIEGE:GET_MATERIAL_FROM_REAGENT:bar:NONE] So, we are going to use the ``eventful`` module to make it so that (after the script is run) when this crossbow is crafted, it will have two handles, each with the material given by the block reagents. @@ -149,7 +149,9 @@ Now, let's make a callback: :: First, we check to see if it the reaction that just happened is relevant to this callback: :: - if not customRawTokens.getToken(reaction, "TRANSFER_HANDLE_MATERIAL_TO_PRODUCT_IMPROVEMENT") then return end + if not customRawTokens.getToken(reaction, "TUTORIAL_MOD_TRANSFER_HANDLE_MATERIAL_TO_PRODUCT_IMPROVEMENT") then + return + end Then, we get the product number listed. Next, for every reagent, if the reagent name starts with "handle" then we get the corresponding item, and... :: @@ -186,7 +188,7 @@ Let's also make some code to modify the fire rate of the siege crossbow. :: return end - local multiplier = tonumber(customRawTokens.getToken(weapon.subtype, "FIRE_RATE_MULTIPLIER")) or 1 + local multiplier = tonumber(customRawTokens.getToken(weapon.subtype, "TUTORIAL_MOD_FIRE_RATE_MULTIPLIER")) or 1 firer.counters.think_counter = math.floor(firer.counters.think_counter * multiplier) end From aa8809833b4048ca2a8d4d0f5489058db67309f7 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Mon, 11 Jul 2022 18:56:28 +0100 Subject: [PATCH 022/121] Do the "running shoes" TODO --- docs/guides/modding-guide.rst | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index ea7ae9eb7..5cafba37d 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -192,7 +192,40 @@ Let's also make some code to modify the fire rate of the siege crossbow. :: firer.counters.think_counter = math.floor(firer.counters.think_counter * multiplier) end -TODO: "running shoes" +Now, let's see how we could make some "pegasus boots". First, let's define the item in the raws: :: + +[ITEM_SHOES:ITEM_SHOES_BOOTS_PEGASUS] + [NAME:pegasus boot:pegasus boots] + [ARMORLEVEL:1] + [UPSTEP:1] + [METAL_ARMOR_LEVELS] + [LAYER:OVER] + [COVERAGE:100] + [LAYER_SIZE:25] + [LAYER_PERMIT:15] + [MATERIAL_SIZE:2] + [METAL] + [LEATHER] + [HARD] + [EXAMPLE_MOD_MOVEMENT_TIMER_REDUCTION_PER_TICK:5] custom raw token (you don't have to say this every time) + +Then, let's make a ``repeat-util`` callback for once a tick: :: + + repeatUtil.scheduleEvery(modId, 1, "ticks", function() + +Let's iterate over every active unit, and for every unit, initialise a variable for how much we are going to take from their movement timer and iterate over all their worn items: :: + + for _, unit in ipairs(df.global.world.units.active) do + local amount = 0 + for _, entry in ipairs(unit.inventory) do + +Now, we will add up the effect of all speed-increasing gear and apply it: :: + + if entry.mode == df.unit_inventory_item.T_mode.Worn then + amount = amount + tonumber((customRawTokens.getToken(entry.item, "EXAMPLE_MOD_MOVEMENT_TIMER_REDUCTION_PER_TICK")) or 0) + end + end + dfhack.units.addMoveTimer(-amount) -- Subtract amount from movement timer if currently moving Your first whole mod -------------------- From c9cffc7da92e25bf9a1032e18a81eb000aac8f73 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Tue, 12 Jul 2022 11:26:49 +0100 Subject: [PATCH 023/121] Fix docs bugs --- docs/guides/modding-guide.rst | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index 5cafba37d..990c0ce96 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -101,7 +101,7 @@ Check the full list of events at https://docs.dfhack.org/en/stable/docs/Lua%20AP Custom raw tokens ----------------- -In this section, we are going to use `custom raw tokens <_custom-raw-tokens>` applied to a reaction to transfer the material of a reagent to a product as a handle improvement (like on artifact buckets), and then we are going to see how you could make boots that make units go faster when worn. Both of these involve custom raw tokens. +In this section, we are going to use `custom raw tokens ` applied to a reaction to transfer the material of a reagent to a product as a handle improvement (like on artifact buckets), and then we are going to see how you could make boots that make units go faster when worn. Both of these involve custom raw tokens. First, let's define a custom crossbow with its own custom reaction. The crossbow: :: @@ -194,20 +194,20 @@ Let's also make some code to modify the fire rate of the siege crossbow. :: Now, let's see how we could make some "pegasus boots". First, let's define the item in the raws: :: -[ITEM_SHOES:ITEM_SHOES_BOOTS_PEGASUS] - [NAME:pegasus boot:pegasus boots] - [ARMORLEVEL:1] - [UPSTEP:1] - [METAL_ARMOR_LEVELS] - [LAYER:OVER] - [COVERAGE:100] - [LAYER_SIZE:25] - [LAYER_PERMIT:15] - [MATERIAL_SIZE:2] - [METAL] - [LEATHER] - [HARD] - [EXAMPLE_MOD_MOVEMENT_TIMER_REDUCTION_PER_TICK:5] custom raw token (you don't have to say this every time) + [ITEM_SHOES:ITEM_SHOES_BOOTS_PEGASUS] + [NAME:pegasus boot:pegasus boots] + [ARMORLEVEL:1] + [UPSTEP:1] + [METAL_ARMOR_LEVELS] + [LAYER:OVER] + [COVERAGE:100] + [LAYER_SIZE:25] + [LAYER_PERMIT:15] + [MATERIAL_SIZE:2] + [METAL] + [LEATHER] + [HARD] + [EXAMPLE_MOD_MOVEMENT_TIMER_REDUCTION_PER_TICK:5] custom raw token (you don't have to say this every time) Then, let's make a ``repeat-util`` callback for once a tick: :: From c39e2fe2cbf88a2e88552f7b09f2bdc28d199dca Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Wed, 13 Jul 2022 22:42:35 +0100 Subject: [PATCH 024/121] Mod structure --- docs/guides/modding-guide.rst | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index 990c0ce96..abc5e5e01 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -232,4 +232,15 @@ Your first whole mod Now, you may have noticed that you won't be able to run multiple functions on tick/as event callbacks with that ``modId`` idea alone. To solve that we can just define all the functions we want and call them from a single function. -TODO +Create a folder for mod projects within your Dwarf Fortress directory somewhere (e.g. ``hack/my-scripts/mods/``) and use your mod ID (in hyphen-case) as the name for the mod folders within it. The structure of and environment for fully-functioning modular mods are as follows: + +* The main content of the mod would be in the ``raw`` folder: + + * A Lua file in ``raw/init.d/`` to initialise the mod by calling ``your-mod-id/main/ enable``. + * Raw content (potentially with custom raw tokens) in ``raw/objects/``. + * A subfolder for your mod in ``raw/scripts/`` containing a ``main.lua`` file (an example of which we will see) and all the modules containing the functions used in callbacks to ``repeat-util`` and ``eventful``. Potentially a file containing constant definitions used by your mod (perhaps defined by the game) too. + +* Using git within each mod folder is recommended, but not required. +* A ``readme.mkd`` markdown file is also recommended. +* An ``addToEntity.txt`` file containing lines to add to entity definitions for access to mod content would be needed if applicable. +* Unless you want to merge your ``raw`` folder with your worlds every time you make a change to your scripts, you should add ``path/to/your-mod/raw/scripts/`` to your script paths. From 9c04a28bd96503e4a2ff1b2e52745b7326f5a4ff Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Wed, 13 Jul 2022 22:43:01 +0100 Subject: [PATCH 025/121] Shouldn't've replaced the TODO --- docs/guides/modding-guide.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index abc5e5e01..d52607798 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -244,3 +244,5 @@ Create a folder for mod projects within your Dwarf Fortress directory somewhere * A ``readme.mkd`` markdown file is also recommended. * An ``addToEntity.txt`` file containing lines to add to entity definitions for access to mod content would be needed if applicable. * Unless you want to merge your ``raw`` folder with your worlds every time you make a change to your scripts, you should add ``path/to/your-mod/raw/scripts/`` to your script paths. + +TODO From d7976e63b60ab3664485f7b16b9c8101a8d8e654 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Wed, 13 Jul 2022 22:44:38 +0100 Subject: [PATCH 026/121] const example. also, (preemptively acquiescing) readme.mkd --> readme.md --- docs/guides/modding-guide.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index d52607798..033ddc731 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -238,10 +238,10 @@ Create a folder for mod projects within your Dwarf Fortress directory somewhere * A Lua file in ``raw/init.d/`` to initialise the mod by calling ``your-mod-id/main/ enable``. * Raw content (potentially with custom raw tokens) in ``raw/objects/``. - * A subfolder for your mod in ``raw/scripts/`` containing a ``main.lua`` file (an example of which we will see) and all the modules containing the functions used in callbacks to ``repeat-util`` and ``eventful``. Potentially a file containing constant definitions used by your mod (perhaps defined by the game) too. + * A subfolder for your mod in ``raw/scripts/`` containing a ``main.lua`` file (an example of which we will see) and all the modules containing the functions used in callbacks to ``repeat-util`` and ``eventful``. Potentially a file containing constant definitions used by your mod (perhaps defined by the game, like the acceleration of parabolic projectiles due to gravity (``4900``)) too. * Using git within each mod folder is recommended, but not required. -* A ``readme.mkd`` markdown file is also recommended. +* A ``readme.md`` markdown file is also recommended. * An ``addToEntity.txt`` file containing lines to add to entity definitions for access to mod content would be needed if applicable. * Unless you want to merge your ``raw`` folder with your worlds every time you make a change to your scripts, you should add ``path/to/your-mod/raw/scripts/`` to your script paths. From a996b29cf5845024ac50dacb7c35c097c28abcda Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Thu, 14 Jul 2022 11:10:23 +0100 Subject: [PATCH 027/121] Some editing of mod guide --- docs/guides/modding-guide.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index 033ddc731..6b18188a0 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -230,9 +230,9 @@ Now, we will add up the effect of all speed-increasing gear and apply it: :: Your first whole mod -------------------- -Now, you may have noticed that you won't be able to run multiple functions on tick/as event callbacks with that ``modId`` idea alone. To solve that we can just define all the functions we want and call them from a single function. +Now, you may have noticed that you won't be able to run multiple functions on tick/as event callbacks with that ``modId`` idea alone. To solve that we can just define all the functions we want and call them from a single function. Alternatively you can create multiple callbacks with your mod ID being a prefix, though this way there is no guarantee about the order if that is important. -Create a folder for mod projects within your Dwarf Fortress directory somewhere (e.g. ``hack/my-scripts/mods/``) and use your mod ID (in hyphen-case) as the name for the mod folders within it. The structure of and environment for fully-functioning modular mods are as follows: +Create a folder for mod projects somewhere (e.g. ``hack/my-scripts/mods/``, or maybe somewhere outside your Dwarf Fortress installation) and use your mod ID (in hyphen-case) as the name for the mod folders within it. The structure of and environment for fully-functioning modular mods are as follows: * The main content of the mod would be in the ``raw`` folder: From d06a63e4dc583732f70bce03945f6da20d1e4e6d Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Thu, 14 Jul 2022 11:11:50 +0100 Subject: [PATCH 028/121] DFStructs (a nickname?) --> df-structures --- docs/guides/modding-guide.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index 6b18188a0..1badad2dd 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -55,8 +55,8 @@ Now, the field ``sex`` in a unit is an integer, but each integer corresponds to Simple. Save this as a Lua file in your own scripts directory and run it as shown before when focused on a unit one way or another. -Getting used to gm-editor and DFStructs exploration ---------------------------------------------------- +Getting used to gm-editor and df-structures exploration +------------------------------------------------------- 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 structures. The first is the ``df-structures`` repository (at https://github.com/DFHack/df-structures) that contains XML files denoting the contents of the game's structures. The second is the script ``gui/gm-editor`` which is an interactive data explorer. You can run the script while objects like units are selected to view the data within them. You can also pass ``scr`` as an argument to the script to view the data for the current screen. Press ? while the script is active to view help. From 3f848b8836ef8eeb6cc720c298539b2e62696998 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Thu, 14 Jul 2022 17:46:12 +0100 Subject: [PATCH 029/121] Misc minor changes to modding guide --- docs/guides/modding-guide.rst | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index 1badad2dd..d0c9cb398 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -22,7 +22,7 @@ Scripts can be run from a world's ``raw/scripts/`` directory, and (configurably) A script is run by writing its path and name from a script path folder without the file extension into a DFHack command prompt (in-game or the external one). E.g. ``gui/gm-editor`` for ``hack/scripts/gui/gm-editor.lua``. -You can make all your scripts in ``hack/scripts/``, but this is not recommended as it makes things much harder to maintain each update. It's recommended to make a folder with a name like "own-scripts" and add it to ``dfhack-config/script-paths.txt``. You should also make a folder for external installed scripts from the internet that are not in ``hack/scripts/``. You can prepend your script paths entries with a ``+`` so that they take precedence over other folders. +You can make all your scripts in ``hack/scripts/``, but this is not recommended as it makes things much harder to maintain each update. It's recommended to make a folder with a name like ``own-scripts`` and add it to ``dfhack-config/script-paths.txt``. You could also make a folder for external installed scripts from the internet that are not in ``hack/scripts/``. You can prepend your script paths entries with a ``+`` so that they take precedence over other folders. If your mod is installed into ``raw/scripts/`` be aware that the copies of the scripts in ``data/save/*/raw/`` are checked first and will run instead of any changes you make to an in-development copy outside of a raw folder. @@ -75,13 +75,13 @@ First, we load the module: :: Both ``repeat-util`` and ``eventful`` require keys for registered callbacks. It's recommended to use something like a mod id. :: - local modId = "my-test-mod" + local modId = "callback-example-mod" Then, we pass the key, amount of time units between function calls, what the time units are, and finally the callback function itself: :: repeatUtil.scheduleEvery(modId, 1, "ticks", function() - -- Do something like iterating over all units - for _, unit in ipairs(df.global.world.units.all) do + -- Do something like iterating over all active units + for _, unit in ipairs(df.global.world.units.active) do print(unit.id) end end) @@ -117,7 +117,7 @@ First, let's define a custom crossbow with its own custom reaction. The crossbow [MATERIAL_SIZE:4] [ATTACK:BLUNT:10000:4000:bash:bashes:NO_SUB:1250] [ATTACK_PREPARE_AND_RECOVER:3:3] - [TUTORIAL_MOD_FIRE_RATE_MULTIPLIER:2] custom token (you'll see) + [SIEGE_CROSSBOW_MOD_FIRE_RATE_MULTIPLIER:2] custom token (you'll see) The reaction to make it (you would add the reaction and not the weapon to an entity raw): :: @@ -132,7 +132,7 @@ The reaction to make it (you would add the reaction and not the weapon to an ent [ANY_PLANT_MATERIAL] [REAGENT:handle 2:1:BLOCKS:NONE:NONE:NONE] [ANY_PLANT_MATERIAL] - [TUTORIAL_MOD_TRANSFER_HANDLE_MATERIAL_TO_PRODUCT_IMPROVEMENT:1] another custom token + [SIEGE_CROSSBOW_MOD_TRANSFER_HANDLE_MATERIAL_TO_PRODUCT_IMPROVEMENT:1] another custom token [PRODUCT:100:1:WEAPON:ITEM_WEAPON_CROSSBOW_SIEGE:GET_MATERIAL_FROM_REAGENT:bar:NONE] So, we are going to use the ``eventful`` module to make it so that (after the script is run) when this crossbow is crafted, it will have two handles, each with the material given by the block reagents. @@ -144,12 +144,12 @@ First, require the modules we are going to use. :: Now, let's make a callback: :: - local modId = "siege-crossbow" + local modId = "siege-crossbow-mod" eventful.onReactionComplete[modId] = function(reaction, reactionProduct, unit, inputItems, inputReagents, outputItems) First, we check to see if it the reaction that just happened is relevant to this callback: :: - if not customRawTokens.getToken(reaction, "TUTORIAL_MOD_TRANSFER_HANDLE_MATERIAL_TO_PRODUCT_IMPROVEMENT") then + if not customRawTokens.getToken(reaction, "SIEGE_CROSSBOW_MOD_TRANSFER_HANDLE_MATERIAL_TO_PRODUCT_IMPROVEMENT") then return end @@ -188,7 +188,7 @@ Let's also make some code to modify the fire rate of the siege crossbow. :: return end - local multiplier = tonumber(customRawTokens.getToken(weapon.subtype, "TUTORIAL_MOD_FIRE_RATE_MULTIPLIER")) or 1 + local multiplier = tonumber(customRawTokens.getToken(weapon.subtype, "SIEGE_CROSSBOW_MOD_FIRE_RATE_MULTIPLIER")) or 1 firer.counters.think_counter = math.floor(firer.counters.think_counter * multiplier) end @@ -207,7 +207,7 @@ Now, let's see how we could make some "pegasus boots". First, let's define the i [METAL] [LEATHER] [HARD] - [EXAMPLE_MOD_MOVEMENT_TIMER_REDUCTION_PER_TICK:5] custom raw token (you don't have to say this every time) + [PEGASUS_BOOTS_MOD_MOVEMENT_TIMER_REDUCTION_PER_TICK:5] custom raw token (you don't have to comment this every time) Then, let's make a ``repeat-util`` callback for once a tick: :: @@ -222,7 +222,7 @@ Let's iterate over every active unit, and for every unit, initialise a variable Now, we will add up the effect of all speed-increasing gear and apply it: :: if entry.mode == df.unit_inventory_item.T_mode.Worn then - amount = amount + tonumber((customRawTokens.getToken(entry.item, "EXAMPLE_MOD_MOVEMENT_TIMER_REDUCTION_PER_TICK")) or 0) + amount = amount + tonumber((customRawTokens.getToken(entry.item, "PEGASUS_BOOTS_MOD_MOVEMENT_TIMER_REDUCTION_PER_TICK")) or 0) end end dfhack.units.addMoveTimer(-amount) -- Subtract amount from movement timer if currently moving From 0e704f39f7cdb4710923df648a000e7db635c0ed Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Fri, 15 Jul 2022 19:28:59 +0100 Subject: [PATCH 030/121] Got the really big part of the guide done --- docs/Lua API.rst | 2 + docs/guides/modding-guide.rst | 97 +++++++++++++++++++++++++++++++++-- 2 files changed, 95 insertions(+), 4 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index f2f252c4b..c459bf1f2 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -5040,6 +5040,8 @@ General script API Importing scripts ================= +.. _reqscript: + * ``dfhack.reqscript(name)`` or ``reqscript(name)`` Loads a Lua script and returns its environment (i.e. a table of all global diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index d0c9cb398..aa07ca8a1 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -227,10 +227,10 @@ Now, we will add up the effect of all speed-increasing gear and apply it: :: end dfhack.units.addMoveTimer(-amount) -- Subtract amount from movement timer if currently moving -Your first whole mod --------------------- +The structure of a full mod +--------------------------- -Now, you may have noticed that you won't be able to run multiple functions on tick/as event callbacks with that ``modId`` idea alone. To solve that we can just define all the functions we want and call them from a single function. Alternatively you can create multiple callbacks with your mod ID being a prefix, though this way there is no guarantee about the order if that is important. +Now, you may have noticed that you won't be able to run multiple functions on tick/as event callbacks with that ``modId`` idea alone. To solve that we can just define all the functions we want and call them from a single function. Alternatively you can create multiple callbacks with your mod ID being a prefix, though this way there is no guarantee about the order if that is important. You will have to use your mod ID as a prefix if you register multiple ``repeat-util`` callbacks, though. Create a folder for mod projects somewhere (e.g. ``hack/my-scripts/mods/``, or maybe somewhere outside your Dwarf Fortress installation) and use your mod ID (in hyphen-case) as the name for the mod folders within it. The structure of and environment for fully-functioning modular mods are as follows: @@ -245,4 +245,93 @@ Create a folder for mod projects somewhere (e.g. ``hack/my-scripts/mods/``, or m * An ``addToEntity.txt`` file containing lines to add to entity definitions for access to mod content would be needed if applicable. * Unless you want to merge your ``raw`` folder with your worlds every time you make a change to your scripts, you should add ``path/to/your-mod/raw/scripts/`` to your script paths. -TODO +Now, let's take a look at an example ``main.lua`` file. :: + + local repeatUtil = require("repeat-util") + local eventful = require("plugins.eventful") + + local modId = "example-mod" + local args = {...} + + if args[1] == "enable" then + -- The modules and what they link into the environment with + -- Each module exports functions named the way they are to be used + local moduleA = dfhack.reqscript("example-mod/module-a") -- on load, every tick + local moduleB = dfhack.reqscript("example-mod/module-b") -- on load, on unload, onReactionComplete + local moduleC = dfhack.reqscript("example-mod/module-c") -- onReactionComplete + local moduleD = dfhack.reqscript("example-mod/module-d") -- every 100 frames, onProjItemCheckMovement, onProjUnitCheckMovement + + -- Set up the modules + -- Order: on load, repeat-util ticks (from smallest interval to largest), days, months, years, and frames, then eventful callbacks in the same order as the first modules to use them + + moduleA.onLoad() + moduleB.onLoad() + + repeatUtil.scheduleEvery(modId .. " 1 ticks", 1, "ticks", function() + moduleA.every1Tick() + end) + + repeatUtil.scheduleEvery(modID .. " 100 frames", 1, "frames", function() + moduleD.every100Frames() + end + + eventful.onReactionComplete[modId] = function(...) + -- Pass the event's parameters to the listeners, whatever they are + moduleB.onReactionComplete(...) + moduleC.onReactionComplete(...) + end + + eventful.onProjItemCheckMovement[modId] = function(...) + moduleD.onProjItemCheckMovement(...) + end + + eventful.onProjUnitCheckMovement[modId] = function(...) + moduleD.onProjUnitCheckMovement(...) + end + + print("Example mod enabled") + elseif args[1] == "disable" then + -- Order: on unload, then cancel the callbacks in the same order as above + + moduleA.onUnload() + + repeatUtil.cancel(modId .. " 1 ticks") + repeatUtil.cancel(modId .. " 100 frames") + + eventful.onReactionComplete[modId] = nil + eventful.onProjItemCheckMovement[modId] = nil + eventful.onProjUnitCheckMovement[modId] = nil + + print("Example mod disabled") + elseif not args[1] then + dfhack.printerr("No argument given to example-mod/main") + else + dfhack.printerr("Unknown argument \"" .. args[1] .. "\" to example-mod/main") + end + +You can see there are four cases depending on arguments. Set up the callbacks and call on-load functions if enabled, dismantle the callbacks and call on-unload functions if disabled, no arguments given, and invalid argument(s) given. + +Here is an example of an ``raw/init.d/`` file: :: + + dfhack.run_command("example-mod/main enable") -- Very simple. Could be called "init-example-mod.lua" + +Here is what ``raw/scripts/module-a.lua`` would look like: :: + + --@ module = true + -- The above line is required for dfhack.reqscript to work + + function onLoad() -- global variables are exported + -- blah + end + + local function usedByOnTick() -- local variables are not exported + -- blah + end + + function onTick() -- exported + for blah in ipairs(blah) do + usedByOnTick() + end + end + +It is recommended to check `reqscript `'s documentation. ``reqscript`` caches scripts but will reload scripts that have changed (it checks the file's last modification date) so you can do live editing *and* have common tables et cetera between scripts that require the same module. From fcd8839c0d81da1e47ab7e2902caf91e27feaa97 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Fri, 15 Jul 2022 19:43:48 +0100 Subject: [PATCH 031/121] Wrap lines to 80 characters --- docs/guides/modding-guide.rst | 213 +++++++++++++++++++++++++--------- 1 file changed, 161 insertions(+), 52 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index aa07ca8a1..cebd34745 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -6,41 +6,70 @@ DFHack modding guide What is a mod/script? --------------------- -A script is a single file that can be run as a command in DFHack, like something that modifies or displays game data on request. A mod is something you install to get persistent behavioural changes in the game and/or add new content. DFHack mods contain and use scripts as well as often having a raw mod component. +A script is a single file that can be run as a command in DFHack, like something +that modifies or displays game data on request. A mod is something you install +to get persistent behavioural changes in the game and/or add new content. DFHack +mods contain and use scripts as well as often having a raw mod component. -DFHack scripts are written in Lua. If you don't already know Lua, there's a great primer at https://www.lua.org/pil/1.html. +DFHack scripts are written in Lua. If you don't already know Lua, there's a +great primer at https://www.lua.org/pil/1.html. Why not just use raw modding? ----------------------------- -For many things it's either completely and only (sensibly) doable in raws or completely and only doable with DFHack. For mods where DFHack is an alternative and not the only option, it's much less hacky, easier to maintain, and easier to extend, and is not prone to side-effects. A great example is adding a syndrome when a reaction is performed requiring an exploding boulder in raws but having dedicated tools for it if you use DFHack. Many things will require a mix of raw modding and DFHack. +For many things it's either completely and only (sensibly) doable in raws or +completely and only doable with DFHack. For mods where DFHack is an alternative +and not the only option, it's much less hacky, easier to maintain, and easier to +extend, and is not prone to side-effects. A great example is adding a syndrome +when a reaction is performed requiring an exploding boulder in raws but having +dedicated tools for it if you use DFHack. Many things will require a mix of raw +modding and DFHack. A mod-maker's development environment ------------------------------------- -Scripts can be run from a world's ``raw/scripts/`` directory, and (configurably) are run by default from ``hack/scripts/``. Scripts in ``raw/init.d/`` are automatically run on world load. Scripts within the raws are a component for more advanced mods. +Scripts can be run from a world's ``raw/scripts/`` directory, and (configurably) +are run by default from ``hack/scripts/``. Scripts in ``raw/init.d/`` are +automatically run on world load. Scripts within the raws are a component for +more advanced mods. -A script is run by writing its path and name from a script path folder without the file extension into a DFHack command prompt (in-game or the external one). E.g. ``gui/gm-editor`` for ``hack/scripts/gui/gm-editor.lua``. +A script is run by writing its path and name from a script path folder without +the file extension into a DFHack command prompt (in-game or the external one). +E.g. ``gui/gm-editor`` for ``hack/scripts/gui/gm-editor.lua``. -You can make all your scripts in ``hack/scripts/``, but this is not recommended as it makes things much harder to maintain each update. It's recommended to make a folder with a name like ``own-scripts`` and add it to ``dfhack-config/script-paths.txt``. You could also make a folder for external installed scripts from the internet that are not in ``hack/scripts/``. You can prepend your script paths entries with a ``+`` so that they take precedence over other folders. +You can make all your scripts in ``hack/scripts/``, but this is not recommended +as it makes things much harder to maintain each update. It's recommended to make +a folder with a name like ``own-scripts`` and add it to +``dfhack-config/script-paths.txt``. You could also make a folder for external +installed scripts from the internet that are not in ``hack/scripts/``. You can +prepend your script paths entries with a ``+`` so that they take precedence over +other folders. -If your mod is installed into ``raw/scripts/`` be aware that the copies of the scripts in ``data/save/*/raw/`` are checked first and will run instead of any changes you make to an in-development copy outside of a raw folder. +If your mod is installed into ``raw/scripts/`` be aware that the copies of the +scripts in ``data/save/*/raw/`` are checked first and will run instead of any +changes you make to an in-development copy outside of a raw folder. The structure of the game ------------------------- -"The game" is in the global variable `df `. The game's memory can be found in ``df.global``, containing things like the list of all items, whether to reindex pathfinding, et cetera. Also relevant to us in ``df`` are the various types found in the game, e.g. ``df.pronoun_type`` which we will be using. +"The game" is in the global variable `df `. The game's memory can be +found in ``df.global``, containing things like the list of all items, whether to +reindex pathfinding, et cetera. Also relevant to us in ``df`` are the various +types found in the game, e.g. ``df.pronoun_type`` which we will be using. Your first script ----------------- -So! It's time to write your first script. We are going to make a script that will get the pronoun type of the currently selected unit (there are many contexts where the function that gets the currently selected unit works). +So! It's time to write your first script. We are going to make a script that +will get the pronoun type of the currently selected unit (there are many +contexts where the function that gets the currently selected unit works). First line, we get the unit. :: local unit = dfhack.gui.getSelectedUnit() -If no unit is selected, an error message will be printed (which can be silenced by passing ``true`` to ``getSelectedUnit``) and ``unit`` will be ``nil``. +If no unit is selected, an error message will be printed (which can be silenced +by passing ``true`` to ``getSelectedUnit``) and ``unit`` will be ``nil``. If ``unit`` is ``nil``, we don't want the script to run anymore. :: @@ -48,36 +77,60 @@ If ``unit`` is ``nil``, we don't want the script to run anymore. :: return end -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 map ``df.pronoun_type`` with an integer from the unit. Indexing the other way, with one of the strings, will yield its corresponding number. So: :: +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 +map ``df.pronoun_type`` with an integer from the unit. Indexing the other way, +with one of the strings, will yield its corresponding number. So: :: local pronounTypeString = df.pronoun_type[unit.sex] print(pronounTypeString) -Simple. Save this as a Lua file in your own scripts directory and run it as shown before when focused on a unit one way or another. +Simple. Save this as a Lua file in your own scripts directory and run it as +shown before when focused on a unit one way or another. Getting used to gm-editor and df-structures exploration ------------------------------------------------------- -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 structures. The first is the ``df-structures`` repository (at https://github.com/DFHack/df-structures) that contains XML files denoting the contents of the game's structures. The second is the script ``gui/gm-editor`` which is an interactive data explorer. You can run the script while objects like units are selected to view the data within them. You can also pass ``scr`` as an argument to the script to view the data for the current screen. Press ? while the script is active to view help. +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 +structures. The first is the ``df-structures`` repository +(at https://github.com/DFHack/df-structures) that contains XML files denoting +the contents of the game's structures. The second is the script +``gui/gm-editor`` which is an interactive data explorer. You can run the script +while objects like units are selected to view the data within them. You can also +pass ``scr`` as an argument to the script to view the data for the current +screen. Press ? while the script is active to view help. -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 (e.g. DFHack's Discord). +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 (e.g. DFHack's +Discord). Detecting triggers ------------------ -One main method for getting new behaviour into the game is callback functions. There are two main libraries for this, ``repeat-util`` and ``eventful``. ``repeat-util`` is used to run a function once per configurable number of frames (paused or unpaused), ticks (unpaused), in-game days, months, or years. For adding behaviour you will most often want something to run once a tick. ``eventful`` is used to get code to run (with special parameters!) when something happens in the game, like a reaction or job being completed or a projectile moving. +One main method for getting new behaviour into the game is callback functions. +There are two main libraries for this, ``repeat-util`` and ``eventful``. +``repeat-util`` is used to run a function once per configurable number of frames +(paused or unpaused), ticks (unpaused), in-game days, months, or years. For +adding behaviour you will most often want something to run once a tick. +``eventful`` is used to get code to run (with special parameters!) when +something happens in the game, like a reaction or job being completed or a +projectile moving. -To get something to run once per tick, we would want to call ``repeat-util``'s ``scheduleEvery`` function. +To get something to run once per tick, we would want to call ``repeat-util``'s +``scheduleEvery`` function. First, we load the module: :: local repeatUtil = require("repeat-util") -Both ``repeat-util`` and ``eventful`` require keys for registered callbacks. It's recommended to use something like a mod id. :: +Both ``repeat-util`` and ``eventful`` require keys for registered callbacks. +It's recommended to use something like a mod id. :: local modId = "callback-example-mod" -Then, we pass the key, amount of time units between function calls, what the time units are, and finally the callback function itself: :: +Then, we pass the key, amount of time units between function calls, what the +time units are, and finally the callback function itself: :: repeatUtil.scheduleEvery(modId, 1, "ticks", function() -- Do something like iterating over all active units @@ -90,20 +143,29 @@ Then, we pass the key, amount of time units between function calls, what the tim local eventful = require("plugins.eventful") -``eventful`` contains a table for each event which you populate with functions. Each function in the table is then called with the appropriate arguments when the event occurs. So, for example, to print the position of a moving (item) projectile: :: +``eventful`` contains a table for each event which you populate with functions. +Each function in the table is then called with the appropriate arguments when +the event occurs. So, for example, to print the position of a moving (item) +projectile: :: eventful.onProjItemCheckMovement[modId] = function(projectile) print(projectile.cur_pos.x, projectile.cur_pos.y, projectile.cur_pos.z) end -Check the full list of events at https://docs.dfhack.org/en/stable/docs/Lua%20API.html#list-of-events. +Check the full list of events at +https://docs.dfhack.org/en/stable/docs/Lua%20API.html#list-of-events. Custom raw tokens ----------------- -In this section, we are going to use `custom raw tokens ` applied to a reaction to transfer the material of a reagent to a product as a handle improvement (like on artifact buckets), and then we are going to see how you could make boots that make units go faster when worn. Both of these involve custom raw tokens. +In this section, we are going to use `custom raw tokens ` +applied to a reaction to transfer the material of a reagent to a product as a +handle improvement (like on artifact buckets), and then we are going to see how +you could make boots that make units go faster when worn. Both of these involve +custom raw tokens. -First, let's define a custom crossbow with its own custom reaction. The crossbow: :: +First, let's define a custom crossbow with its own custom reaction. The +crossbow: :: [ITEM_WEAPON:ITEM_WEAPON_CROSSBOW_SIEGE] [NAME:crossbow:crossbows] @@ -119,7 +181,8 @@ First, let's define a custom crossbow with its own custom reaction. The crossbow [ATTACK_PREPARE_AND_RECOVER:3:3] [SIEGE_CROSSBOW_MOD_FIRE_RATE_MULTIPLIER:2] custom token (you'll see) -The reaction to make it (you would add the reaction and not the weapon to an entity raw): :: +The reaction to make it (you would add the reaction and not the weapon to an +entity raw): :: [REACTION:MAKE_SIEGE_CROSSBOW] [NAME:make siege crossbow] @@ -132,10 +195,13 @@ The reaction to make it (you would add the reaction and not the weapon to an ent [ANY_PLANT_MATERIAL] [REAGENT:handle 2:1:BLOCKS:NONE:NONE:NONE] [ANY_PLANT_MATERIAL] - [SIEGE_CROSSBOW_MOD_TRANSFER_HANDLE_MATERIAL_TO_PRODUCT_IMPROVEMENT:1] another custom token + [SIEGE_CROSSBOW_MOD_TRANSFER_HANDLE_MATERIAL_TO_PRODUCT_IMPROVEMENT:1] + another custom token [PRODUCT:100:1:WEAPON:ITEM_WEAPON_CROSSBOW_SIEGE:GET_MATERIAL_FROM_REAGENT:bar:NONE] -So, we are going to use the ``eventful`` module to make it so that (after the script is run) when this crossbow is crafted, it will have two handles, each with the material given by the block reagents. +So, we are going to use the ``eventful`` module to make it so that (after the +script is run) when this crossbow is crafted, it will have two handles, each +with the material given by the block reagents. First, require the modules we are going to use. :: @@ -145,15 +211,20 @@ First, require the modules we are going to use. :: Now, let's make a callback: :: local modId = "siege-crossbow-mod" - eventful.onReactionComplete[modId] = function(reaction, reactionProduct, unit, inputItems, inputReagents, outputItems) + eventful.onReactionComplete[modId] = function(reaction, reactionProduct, + unit, inputItems, inputReagents, outputItems) -First, we check to see if it the reaction that just happened is relevant to this callback: :: +First, we check to see if it the reaction that just happened is relevant to this +callback: :: - if not customRawTokens.getToken(reaction, "SIEGE_CROSSBOW_MOD_TRANSFER_HANDLE_MATERIAL_TO_PRODUCT_IMPROVEMENT") then + if not customRawTokens.getToken(reaction, + "SIEGE_CROSSBOW_MOD_TRANSFER_HANDLE_MATERIAL_TO_PRODUCT_IMPROVEMENT") + then return end -Then, we get the product number listed. Next, for every reagent, if the reagent name starts with "handle" then we get the corresponding item, and... :: +Then, we get the product number listed. Next, for every reagent, if the reagent +name starts with "handle" then we get the corresponding item, and... :: for i, reagent in ipairs(inputReagents) do if reagent.code:sub(1, #"handle") == "handle" then @@ -167,9 +238,9 @@ Then, we get the product number listed. Next, for every reagent, if the reagent -- new.maker = outputItems[0].maker -- not a typical improvement new.type = df.itemimprovement_specific_type.HANDLE outputItems[productNumber - 1].improvements:insert("#", new) - -- break -- multiple handles, multiple "the handle is made from"s, so no break -It's all a bit loose and hacky but it works, at least if you don't have multiple stacks filling up one reagent. +It's all a bit loose and hacky but it works, at least if you don't have multiple +stacks filling up one reagent. Let's also make some code to modify the fire rate of the siege crossbow. :: @@ -189,10 +260,12 @@ Let's also make some code to modify the fire rate of the siege crossbow. :: end local multiplier = tonumber(customRawTokens.getToken(weapon.subtype, "SIEGE_CROSSBOW_MOD_FIRE_RATE_MULTIPLIER")) or 1 - firer.counters.think_counter = math.floor(firer.counters.think_counter * multiplier) + firer.counters.think_counter = math.floor(firer.counters.think_counter * + multiplier) end -Now, let's see how we could make some "pegasus boots". First, let's define the item in the raws: :: +Now, let's see how we could make some "pegasus boots". First, let's define the +item in the raws: :: [ITEM_SHOES:ITEM_SHOES_BOOTS_PEGASUS] [NAME:pegasus boot:pegasus boots] @@ -207,13 +280,16 @@ Now, let's see how we could make some "pegasus boots". First, let's define the i [METAL] [LEATHER] [HARD] - [PEGASUS_BOOTS_MOD_MOVEMENT_TIMER_REDUCTION_PER_TICK:5] custom raw token (you don't have to comment this every time) + [PEGASUS_BOOTS_MOD_MOVEMENT_TIMER_REDUCTION_PER_TICK:5] custom raw token + (you don't have to comment this every time) Then, let's make a ``repeat-util`` callback for once a tick: :: repeatUtil.scheduleEvery(modId, 1, "ticks", function() -Let's iterate over every active unit, and for every unit, initialise a variable for how much we are going to take from their movement timer and iterate over all their worn items: :: +Let's iterate over every active unit, and for every unit, initialise a variable +for how much we are going to take from their movement timer and iterate over all +their worn items: :: for _, unit in ipairs(df.global.world.units.active) do local amount = 0 @@ -225,25 +301,44 @@ Now, we will add up the effect of all speed-increasing gear and apply it: :: amount = amount + tonumber((customRawTokens.getToken(entry.item, "PEGASUS_BOOTS_MOD_MOVEMENT_TIMER_REDUCTION_PER_TICK")) or 0) end end - dfhack.units.addMoveTimer(-amount) -- Subtract amount from movement timer if currently moving + dfhack.units.addMoveTimer(-amount) -- Subtract amount from movement timer if + currently moving The structure of a full mod --------------------------- -Now, you may have noticed that you won't be able to run multiple functions on tick/as event callbacks with that ``modId`` idea alone. To solve that we can just define all the functions we want and call them from a single function. Alternatively you can create multiple callbacks with your mod ID being a prefix, though this way there is no guarantee about the order if that is important. You will have to use your mod ID as a prefix if you register multiple ``repeat-util`` callbacks, though. +Now, you may have noticed that you won't be able to run multiple functions on +tick/as event callbacks with that ``modId`` idea alone. To solve that we can +just define all the functions we want and call them from a single function. +Alternatively you can create multiple callbacks with your mod ID being a prefix, +though this way there is no guarantee about the order if that is important. You +will have to use your mod ID as a prefix if you register multiple +``repeat-util`` callbacks, though. -Create a folder for mod projects somewhere (e.g. ``hack/my-scripts/mods/``, or maybe somewhere outside your Dwarf Fortress installation) and use your mod ID (in hyphen-case) as the name for the mod folders within it. The structure of and environment for fully-functioning modular mods are as follows: +Create a folder for mod projects somewhere (e.g. ``hack/my-scripts/mods/``, or +maybe somewhere outside your Dwarf Fortress installation) and use your mod ID +(in hyphen-case) as the name for the mod folders within it. The structure of and +environment for fully-functioning modular mods are as follows: * The main content of the mod would be in the ``raw`` folder: - * A Lua file in ``raw/init.d/`` to initialise the mod by calling ``your-mod-id/main/ enable``. + * A Lua file in ``raw/init.d/`` to initialise the mod by calling + ``your-mod-id/main/ enable``. * Raw content (potentially with custom raw tokens) in ``raw/objects/``. - * A subfolder for your mod in ``raw/scripts/`` containing a ``main.lua`` file (an example of which we will see) and all the modules containing the functions used in callbacks to ``repeat-util`` and ``eventful``. Potentially a file containing constant definitions used by your mod (perhaps defined by the game, like the acceleration of parabolic projectiles due to gravity (``4900``)) too. + * A subfolder for your mod in ``raw/scripts/`` containing a ``main.lua`` file + (an example of which we will see) and all the modules containing the functions + used in callbacks to ``repeat-util`` and ``eventful``. Potentially a file + containing constant definitions used by your mod (perhaps defined by the + game, like the acceleration of parabolic projectiles due to gravity + (``4900``)) too. * Using git within each mod folder is recommended, but not required. * A ``readme.md`` markdown file is also recommended. -* An ``addToEntity.txt`` file containing lines to add to entity definitions for access to mod content would be needed if applicable. -* Unless you want to merge your ``raw`` folder with your worlds every time you make a change to your scripts, you should add ``path/to/your-mod/raw/scripts/`` to your script paths. +* An ``addToEntity.txt`` file containing lines to add to entity definitions for + access to mod content would be needed if applicable. +* Unless you want to merge your ``raw`` folder with your worlds every time you + make a change to your scripts, you should add + ``path/to/your-mod/raw/scripts/`` to your script paths. Now, let's take a look at an example ``main.lua`` file. :: @@ -256,13 +351,19 @@ Now, let's take a look at an example ``main.lua`` file. :: if args[1] == "enable" then -- The modules and what they link into the environment with -- Each module exports functions named the way they are to be used - local moduleA = dfhack.reqscript("example-mod/module-a") -- on load, every tick - local moduleB = dfhack.reqscript("example-mod/module-b") -- on load, on unload, onReactionComplete - local moduleC = dfhack.reqscript("example-mod/module-c") -- onReactionComplete - local moduleD = dfhack.reqscript("example-mod/module-d") -- every 100 frames, onProjItemCheckMovement, onProjUnitCheckMovement + local moduleA = dfhack.reqscript("example-mod/module-a") -- on load, + -- every tick + local moduleB = dfhack.reqscript("example-mod/module-b") -- on load, + -- on unload, onReactionComplete + local moduleC = dfhack.reqscript("example-mod/module-c") + -- onReactionComplete + local moduleD = dfhack.reqscript("example-mod/module-d") -- every 100 + -- frames, onProjItemCheckMovement, onProjUnitCheckMovement -- Set up the modules - -- Order: on load, repeat-util ticks (from smallest interval to largest), days, months, years, and frames, then eventful callbacks in the same order as the first modules to use them + -- Order: on load, repeat-util ticks (from smallest interval to + -- largest), days, months, years, and frames, then eventful callbacks in + -- the same order as the first modules to use them moduleA.onLoad() moduleB.onLoad() @@ -291,7 +392,8 @@ Now, let's take a look at an example ``main.lua`` file. :: print("Example mod enabled") elseif args[1] == "disable" then - -- Order: on unload, then cancel the callbacks in the same order as above + -- Order: on unload, then cancel the callbacks in the same order as + -- above moduleA.onUnload() @@ -306,14 +408,18 @@ Now, let's take a look at an example ``main.lua`` file. :: elseif not args[1] then dfhack.printerr("No argument given to example-mod/main") else - dfhack.printerr("Unknown argument \"" .. args[1] .. "\" to example-mod/main") + dfhack.printerr("Unknown argument \"" .. args[1] .. + "\" to example-mod/main") end -You can see there are four cases depending on arguments. Set up the callbacks and call on-load functions if enabled, dismantle the callbacks and call on-unload functions if disabled, no arguments given, and invalid argument(s) given. +You can see there are four cases depending on arguments. Set up the callbacks +and call on load functions if enabled, dismantle the callbacks and call on +unload functions if disabled, no arguments given, and invalid argument(s) given. Here is an example of an ``raw/init.d/`` file: :: - dfhack.run_command("example-mod/main enable") -- Very simple. Could be called "init-example-mod.lua" + dfhack.run_command("example-mod/main enable") -- Very simple. Could be + -- called "init-example-mod.lua" Here is what ``raw/scripts/module-a.lua`` would look like: :: @@ -334,4 +440,7 @@ Here is what ``raw/scripts/module-a.lua`` would look like: :: end end -It is recommended to check `reqscript `'s documentation. ``reqscript`` caches scripts but will reload scripts that have changed (it checks the file's last modification date) so you can do live editing *and* have common tables et cetera between scripts that require the same module. +It is recommended to check `reqscript `'s documentation. +``reqscript`` caches scripts but will reload scripts that have changed (it +checks the file's last modification date) so you can do live editing *and* have +common tables et cetera between scripts that require the same module. From 7cf5a7dac90a5675c90336e67de56dad94d22aa9 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Fri, 15 Jul 2022 19:44:21 +0100 Subject: [PATCH 032/121] main.lua --> raw/scripts/main.lua --- docs/guides/modding-guide.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index cebd34745..e35422aef 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -340,7 +340,7 @@ environment for fully-functioning modular mods are as follows: make a change to your scripts, you should add ``path/to/your-mod/raw/scripts/`` to your script paths. -Now, let's take a look at an example ``main.lua`` file. :: +Now, let's take a look at an example ``raw/scripts/main.lua`` file. :: local repeatUtil = require("repeat-util") local eventful = require("plugins.eventful") From bd4d17d2058e5a92a80937caeee7f7f4e12604c7 Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 1 Sep 2022 22:48:18 -0700 Subject: [PATCH 033/121] update changelog --- docs/changelog.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index d1547d34c..0c30f22f5 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -44,6 +44,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Fixes - ``job.removeJob()``: ensure jobs are removed from the world list when they are canceled +- `quickfort`: `Dreamfort ` blueprint set: declare the hospital zone before building the coffer; otherwise DF fails to stock the hospital with materials ## Misc Improvements - Init scripts: ``dfhack.init`` and other init scripts have moved to ``dfhack-config/init/``. If you have customized your ``dfhack.init`` file and want to keep your changes, please move the part that you have customized to the new location at ``dfhack-config/init/dfhack.init``. If you do not have changes that you want to keep, do not copy anything, and the new defaults will be used automatically. @@ -59,10 +60,10 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `seedwatch`: ``seedwatch all`` now adds all plants with seeds to the watchlist, not just the "basic" crops. - UX: You can now move the cursor around in DFHack text fields in ``gui/`` scripts (e.g. `gui/blueprint`, `gui/quickfort`, or `gui/gm-editor`). You can move the cursor by clicking where you want it to go with the mouse or using the Left/Right arrow keys. Ctrl+Left/Right will move one word at a time, and Alt+Left/Right will move to the beginning/end of the text. - UX: You can now click on the hotkey hint text in many ``gui/`` script windows to activate the hotkey, like a button. Not all scripts have been updated to use the clickable widget yet, but you can try it in `gui/blueprint` or `gui/quickfort`. -- `quickfort`: `Dreamfort ` blueprint set improvements: set traffic designations to encourage dwarves to eat in the grand hall instead of the manager's office and to eat cooked food instead of raw ingredients +- `quickfort`: `Dreamfort ` blueprint set improvements: set traffic designations to encourage dwarves to eat cooked food instead of raw ingredients ## Documentation -- Update all DFHack tool documentation with standard syntax formatting, usage examples, and overall clarified text. +- Update all DFHack tool documentation (300+ pages) with standard syntax formatting, usage examples, and overall clarified text. - Group DFHack tools by `tag ` so similar tools are grouped and easy to find ## API From 5441b71957989dbe1fc132305ac8dae3eea70460 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Fri, 2 Sep 2022 07:24:33 +0000 Subject: [PATCH 034/121] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 7bae77756..da571c36a 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 7bae77756f974aad765251096516a89d9d035977 +Subproject commit da571c36ac7e3d4d432a13d1956414f2c20e065e From 587014b7b937ffc18664829cb64f77a9fae983e3 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 2 Sep 2022 11:25:41 -0400 Subject: [PATCH 035/121] Update clsocket dfhack/clsocket#27 --- depends/clsocket | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depends/clsocket b/depends/clsocket index ae19aebd7..6ed8aa464 160000 --- a/depends/clsocket +++ b/depends/clsocket @@ -1 +1 @@ -Subproject commit ae19aebd795d6d91803e60f46de037b604593cb4 +Subproject commit 6ed8aa46462ea01a1122fc49422840a2facc9757 From ef1e85e6ff7131c3eb19c5dbe603efccdcb1bdd8 Mon Sep 17 00:00:00 2001 From: Myk Date: Sat, 3 Sep 2022 20:00:38 -0700 Subject: [PATCH 036/121] Make doc docs match the docs --- docs/Documentation.rst | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/docs/Documentation.rst b/docs/Documentation.rst index fccf59867..8e4ed229b 100644 --- a/docs/Documentation.rst +++ b/docs/Documentation.rst @@ -16,10 +16,10 @@ Python tool named `Sphinx `_. The DFHack build process will compile and install the documentation so it can be displayed in-game by the `help` and `ls` commands (and any other command or GUI that -displays help text), but this is disabled by default due to the additional Python and -Sphinx requirements. If you already have a version of the docs installed (say from a -downloaded release binary), then you only need to build the docs if you're changing them -and want to see the changes reflected in your game. +displays help text), but documentation compilation is disabled by default due to the +additional Python and Sphinx requirements. If you already have a version of the docs +installed (say from a downloaded release binary), then you only need to build the docs +if you're changing them and want to see the changes reflected in your game. You can also build the docs if you just want a local HTML- or text-rendered copy, though you can always read the `online version `_ too. @@ -39,7 +39,7 @@ The source ``.rst`` files are compiled to HTML for viewing in a browser and to t format for viewing in-game. For in-game help, the help text is read from its installed location in ``hack/docs`` under the DF directory. -When writing documentation, remember that everything should be documented! If it's not +When writing documentation, remember that everything should be documented! If it's not clear *where* a particular thing should be documented, ask on Discord or in the DFHack thread on Bay12 -- you'll not only be getting help, you'll also be providing valuable feedback that makes it easier for future contributers to find documentation on how to @@ -183,11 +183,14 @@ And documentation for the ``autodump`` plugin might look like:: Usage help ---------- -The first section after the header and introductory text should be the usage block. You can +The first section after the header and introductory text should be the usage section. You can choose between two formats, based on whatever is cleaner or clearer for your syntax. The first option is to show usage formats together, with an explanation following the block:: - Usage:: + Usage + ----- + + :: build-now [] build-now here [] @@ -206,7 +209,8 @@ option is to show usage formats together, with an explanation following the bloc The second option is to arrange the usage options in a list, with the full command and arguments in monospaced font. Then indent the next line and describe the effect:: - Usage: + Usage + ----- ``build-now []`` Scan the entire map and build all unsuspended constructions @@ -224,6 +228,9 @@ Note that in both options, the entire commandline syntax is written, including t Literal text is written as-is (e.g. the word ``here`` in the above example), and text that describes the kind of parameter that is being passed (e.g. ``pos`` or ``options``) is enclosed in angle brackets (``<`` and ``>``). Optional elements are enclosed in square brackets (``[`` and ``]``). +If the command takes an arbitrary number of elements, use ``...``, for example:: + + prioritize [] [ ...] Examples -------- From 446c321c62e907a9f219cb93a95d19682fecb087 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 4 Sep 2022 12:27:50 -0700 Subject: [PATCH 037/121] fix spacing error and floorplan error --- data/blueprints/library/dreamfort.csv | 8 ++++---- docs/Documentation.rst | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/data/blueprints/library/dreamfort.csv b/data/blueprints/library/dreamfort.csv index 72e2161fe..e26650943 100644 --- a/data/blueprints/library/dreamfort.csv +++ b/data/blueprints/library/dreamfort.csv @@ -865,8 +865,8 @@ Feel free to assign an unimportant animal to the pasture in the main entranceway ,,,`,,`,Cf,Cf,Cf,Cf,Cf,Cf,Cf,Cf,`,~,,,,,,~,`,,,,,,,Cf,Cf,`,,` ,,,`,,`,Cf,Cf,Cf,Cf,Cf,Cf,Cf,Cf,~,~,Cf,Cf,Cf,Cf,Cf,~,~,,,,,,,,Cf,`,,` ,,,`,,`,Cf,Cf,Cf,Cf,Cf,Cf,Cf,Cf,~,~,,~,~,~,,~,~,,,,,Cf,Cf,,Cf,`,,` -,,,`,,`,,Cf,,Cf,Cf,Cf,Cf,Cf,,Cf,,~,~,~,,Cf,,,,,,,,,Cf,`,,` -,,,`,,`,,Cf,,Cf,Cf,Cf,Cf,Cf,`,,,~,~,~,,,`,,,,,,,Cf,Cf,`,,` +,,,`,,`,,Cf,,Cf,Cf,Cf,Cf,Cf,,Cf,,~,~,~,,Cf,,,Cf,,,,,,Cf,`,,` +,,,`,,`,,Cf,,Cf,Cf,Cf,Cf,Cf,`,,,~,~,~,,,`,,Cf,,,,,Cf,Cf,`,,` ,,,`,,`,`,`,`,`,`,`,`,`,`,,,,,,,,`,`,`,`,`,`,`,`,`,`,,` ,,,`,Cf,,,,,,,,,Cf,,,,,~,,,,,Cf,,,,,,,,,Cf,` ,,,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,,,`,`,`,`,`,`,`,`,`,`,`,` @@ -1143,8 +1143,8 @@ t1(37x33) ,,,`,,`,~,~,~,~,~,~,~,~,`,~,~,~,~,~,~,~,`,Cf,Cf,Cf,Cf,Cf,Cf,~,~,`,,` ,,,`,,`,~,~,~,~,~,~,~,~,~,~,~,~,~,~,~,~,~,Cf,Cf,Cf,Cf,Cf,Cf,Cf,~,`,,` ,,,`,,`,~,~,~,~,~,~,~,~,~,~,Cf,~,~,~,Cf,~,~,Cf,Cf,Cf,Cf,~,~,Cf,~,`,,` -,,,`,,`,Cf,~,Cf,~,~,~,~,~,~,~,Cf,~,~,~,Cf,~,~,Cf,Cf,Cf,Cf,Cf,Cf,Cf,~,`,,` -,,,`,,`,Cf,~,Cf,~,~,~,~,~,`,Cf,Cf,~,~,~,Cf,Cf,`,Cf,Cf,Cf,Cf,Cf,Cf,~,~,`,,` +,,,`,,`,Cf,~,Cf,~,~,~,~,~,~,~,Cf,~,~,~,Cf,~,~,Cf,~,Cf,Cf,Cf,Cf,Cf,~,`,,` +,,,`,,`,Cf,~,Cf,~,~,~,~,~,`,Cf,Cf,~,~,~,Cf,Cf,`,Cf,~,Cf,Cf,Cf,Cf,~,~,`,,` ,,,`,,`,`,`,`,`,`,`,`,`,`,Cf,Cf,Cf,Cf,Cf,Cf,Cf,`,`,`,`,`,`,`,`,`,`,,` ,,,`,~,,,,,,,,,,,Cf,Cf,Cf,~,Cf,Cf,Cf,,,,,,,,,,,~,` ,,,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,,,`,`,`,`,`,`,`,`,`,`,`,` diff --git a/docs/Documentation.rst b/docs/Documentation.rst index 8e4ed229b..1d30e72ff 100644 --- a/docs/Documentation.rst +++ b/docs/Documentation.rst @@ -189,7 +189,7 @@ option is to show usage formats together, with an explanation following the bloc Usage ----- - + :: build-now [] From e399e2205bbb2fbff3b097565da0e25260d46104 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 4 Sep 2022 22:28:03 -0700 Subject: [PATCH 038/121] tombstone fix/fat-dwarves --- docs/Removed.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/Removed.rst b/docs/Removed.rst index c39e0c370..442a2695a 100644 --- a/docs/Removed.rst +++ b/docs/Removed.rst @@ -62,6 +62,12 @@ fix/build-location ================== The corresponding DF :bug:`5991` was fixed in DF 0.40.05. +.. _fix/fat-dwarves: + +fix/fat-dwarves +=============== +The corresponding DF :bug:`5971` was fixed in DF 0.40.05. + .. _fortplan: fortplan From d07bc349f89a7133b02221789d2144bf6494a157 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 4 Sep 2022 23:05:16 -0700 Subject: [PATCH 039/121] tombstone fix/diplomats --- docs/Removed.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/Removed.rst b/docs/Removed.rst index 442a2695a..728a0b6eb 100644 --- a/docs/Removed.rst +++ b/docs/Removed.rst @@ -62,6 +62,12 @@ fix/build-location ================== The corresponding DF :bug:`5991` was fixed in DF 0.40.05. +.. _fix/diplomats: + +fix/diplomats +============= +The corresponding DF :bug:`3295` was fixed in DF 0.40.05. + .. _fix/fat-dwarves: fix/fat-dwarves From df244cd5196eec09119946b3e55bd4e9edc22855 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 4 Sep 2022 23:26:18 -0700 Subject: [PATCH 040/121] tombstone fix/feeding-timers --- docs/Removed.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/Removed.rst b/docs/Removed.rst index 728a0b6eb..5144c16f3 100644 --- a/docs/Removed.rst +++ b/docs/Removed.rst @@ -74,6 +74,12 @@ fix/fat-dwarves =============== The corresponding DF :bug:`5971` was fixed in DF 0.40.05. +.. _fix/feeding-timers: + +fix/feeding-timers +================== +The corresponding DF :bug:`2606` was fixed in DF 0.40.12. + .. _fortplan: fortplan From c0682625f960ce57f13100e5835f4ea3bc8b15b8 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 4 Sep 2022 23:27:57 -0700 Subject: [PATCH 041/121] tombstone fix/merchants --- docs/Removed.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/Removed.rst b/docs/Removed.rst index 5144c16f3..f5ab01896 100644 --- a/docs/Removed.rst +++ b/docs/Removed.rst @@ -80,6 +80,12 @@ fix/feeding-timers ================== The corresponding DF :bug:`2606` was fixed in DF 0.40.12. +.. _fix/merchants: + +fix/merchants +============= +Humans can now make trade agreements. This fix is no longer necessary. + .. _fortplan: fortplan From 98f273a10eacbe4a85bc1bc83742b79d8519d5ee Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Mon, 5 Sep 2022 06:45:22 +0000 Subject: [PATCH 042/121] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index da571c36a..73bfcbc0f 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit da571c36ac7e3d4d432a13d1956414f2c20e065e +Subproject commit 73bfcbc0f4e89733ba7b62d134bc0a3b55dc41e8 From 73b7f13ad6d3f284d89c2e76fcfc734a661e867d Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 5 Sep 2022 14:28:35 -0700 Subject: [PATCH 043/121] tombstone gui/assign-rack --- docs/Removed.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/Removed.rst b/docs/Removed.rst index f5ab01896..f9bf1c62e 100644 --- a/docs/Removed.rst +++ b/docs/Removed.rst @@ -96,6 +96,14 @@ script instead. You can use your existing .csv files. Just move them to the ``blueprints`` folder in your DF installation, and instead of ``fortplan file.csv`` run ``quickfort run file.csv``. +.. _gui/assign-rack: + +gui/assign-rack +=============== +This script is no longer useful in current DF versions. The script required a +binpatch `, which has not been available since DF +0.34.11. + .. _gui/hack-wish: gui/hack-wish From e37b7650fa3875e29f1987536c08cf88df684336 Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 5 Sep 2022 20:53:44 -0700 Subject: [PATCH 044/121] update guides including fixing the ordering of the TOC, which, while in alphabetical order, was not in a logical order --- docs/guides/examples-guide.rst | 10 +++++----- docs/guides/index.rst | 6 ++++-- docs/guides/quickfort-alias-guide.rst | 7 ++++--- docs/guides/quickfort-library-guide.rst | 4 ++-- docs/guides/quickfort-user-guide.rst | 5 ++--- 5 files changed, 17 insertions(+), 15 deletions(-) diff --git a/docs/guides/examples-guide.rst b/docs/guides/examples-guide.rst index f61ada4d7..74f94c20b 100644 --- a/docs/guides/examples-guide.rst +++ b/docs/guides/examples-guide.rst @@ -1,8 +1,8 @@ .. _config-examples-guide: .. _dfhack-examples-guide: -DFHack Example Configuration File Index -======================================= +DFHack Config File Examples +=========================== The :source:`hack/examples ` folder contains ready-to-use examples of various DFHack configuration files. You can use them by copying them @@ -14,8 +14,8 @@ The ``init/`` subfolder ----------------------- The :source:`init/ ` subfolder contains useful DFHack -`init-files` that you can copy into your main Dwarf Fortress folder -- the same -directory as ``dfhack.init``. +`init-files` that you can copy into your :file:`dfhack-config/init` folder -- +the same directory as ``dfhack.init``. .. _onMapLoad-dreamfort-init: @@ -35,7 +35,7 @@ it is useful (and customizable) for any fort. It includes the following config: - Periodically enqueues orders to shear and milk shearable and milkable pets. - Sets up `autofarm` to grow 30 units of every crop, except for pig tails, which is set to 150 units to support the textile industry. -- Sets up `seedwatch` to keep 30 of every type of seed. +- Sets up `seedwatch` to protect 30 of every type of seed. - Configures `prioritize` to automatically boost the priority of important and time-sensitive tasks that could otherwise get ignored in busy forts, like hauling food, tanning hides, storing items in vehicles, pulling levers, and diff --git a/docs/guides/index.rst b/docs/guides/index.rst index 7208b5276..d0125921f 100644 --- a/docs/guides/index.rst +++ b/docs/guides/index.rst @@ -6,6 +6,8 @@ These pages are detailed guides covering DFHack tools. .. toctree:: :maxdepth: 1 - :glob: - * + /docs/guides/examples-guide + /docs/guides/quickfort-library-guide + /docs/guides/quickfort-user-guide + /docs/guides/quickfort-alias-guide diff --git a/docs/guides/quickfort-alias-guide.rst b/docs/guides/quickfort-alias-guide.rst index 6afad7569..d4be8aecd 100644 --- a/docs/guides/quickfort-alias-guide.rst +++ b/docs/guides/quickfort-alias-guide.rst @@ -1,10 +1,11 @@ .. _quickfort-alias-guide: -Quickfort Keystroke Alias Guide -=============================== +Quickfort Keystroke Alias Reference +=================================== Aliases allow you to use simple words to represent complicated key sequences -when configuring buildings and stockpiles in quickfort ``#query`` blueprints. +when configuring buildings and stockpiles in quickfort ``#query`` and +``#config`` blueprints. For example, say you have the following ``#build`` and ``#place`` blueprints:: diff --git a/docs/guides/quickfort-library-guide.rst b/docs/guides/quickfort-library-guide.rst index be76c7afe..de5409ab3 100644 --- a/docs/guides/quickfort-library-guide.rst +++ b/docs/guides/quickfort-library-guide.rst @@ -1,8 +1,8 @@ .. _blueprint-library-guide: .. _quickfort-library-guide: -Blueprint Library Index -======================= +Quickfort Blueprint Library +=========================== This guide contains a high-level overview of the blueprints available in the :source:`quickfort blueprint library `. diff --git a/docs/guides/quickfort-user-guide.rst b/docs/guides/quickfort-user-guide.rst index 0af0924b0..9c7d3ed44 100644 --- a/docs/guides/quickfort-user-guide.rst +++ b/docs/guides/quickfort-user-guide.rst @@ -29,9 +29,8 @@ For those just looking to apply existing blueprints, check out the `quickfort command's documentation ` for syntax. There are also many ready-to-use blueprints available in the ``blueprints/library`` subfolder in your DFHack installation. Browse them on your computer or -:source:`online `, or run ``quickfort list`` at the -``[DFHack]#`` prompt to list them, and then ``quickfort run`` to apply them to -your fort! +:source:`online `, or run `gui/quickfort` to browse +and apply them to your fort! Before you become an expert at writing blueprints, though, you should know that the easiest way to make a quickfort blueprint is to build your plan "for real" From 0da9d01916cbe226507a9d87f4a842d41ea8314e Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Tue, 6 Sep 2022 05:56:18 +0000 Subject: [PATCH 045/121] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 73bfcbc0f..c778cb562 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 73bfcbc0f4e89733ba7b62d134bc0a3b55dc41e8 +Subproject commit c778cb56253d26527833b6f55de40c39638de366 From 36de6e6e536a97dabdbae2ab47c99530c51326c1 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Tue, 6 Sep 2022 06:02:12 +0000 Subject: [PATCH 046/121] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index c778cb562..1aae17d6f 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit c778cb56253d26527833b6f55de40c39638de366 +Subproject commit 1aae17d6fa0e4cf1d130c0d94aa8017495563fa6 From 319bfa69195a16893adf7900797263fb67483a9b Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 7 Sep 2022 03:20:19 +0000 Subject: [PATCH 047/121] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 1aae17d6f..776ed6357 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 1aae17d6fa0e4cf1d130c0d94aa8017495563fa6 +Subproject commit 776ed63579f029f9e17b0b96bd301930a15a894e From 587577ef43fbc73cf6215eb6a7db127fb79fb837 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 7 Sep 2022 03:23:29 +0000 Subject: [PATCH 048/121] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 776ed6357..b6f689540 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 776ed63579f029f9e17b0b96bd301930a15a894e +Subproject commit b6f689540eb490973124a29db44546d07c730b6e From e5656f65bf3c038cb781aa3513b95d288d4bb04a Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 7 Sep 2022 07:41:41 +0000 Subject: [PATCH 049/121] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index b6f689540..e6e25ea37 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit b6f689540eb490973124a29db44546d07c730b6e +Subproject commit e6e25ea3720489fbf15e266c3c604980b35d499f From 4e8bb1cdf49524b0eac416a8ca9601e8650ccfe5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 5 Sep 2022 21:39:15 +0000 Subject: [PATCH 050/121] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/python-jsonschema/check-jsonschema: 0.17.1 → 0.18.2](https://github.com/python-jsonschema/check-jsonschema/compare/0.17.1...0.18.2) - [github.com/Lucas-C/pre-commit-hooks: v1.3.0 → v1.3.1](https://github.com/Lucas-C/pre-commit-hooks/compare/v1.3.0...v1.3.1) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 125b1e931..25f043624 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,11 +20,11 @@ repos: args: ['--fix=lf'] - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.17.1 + rev: 0.18.2 hooks: - id: check-github-workflows - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.3.0 + rev: v1.3.1 hooks: - id: forbid-tabs exclude_types: From 483652e1930194c66e01bec332971b6a3b7e26bb Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 4 Sep 2022 23:47:20 -0400 Subject: [PATCH 051/121] Rewrite utils.df_expr_to_ref() for consistency, add tests --- docs/changelog.txt | 1 + library/lua/utils.lua | 14 ++++++++------ test/library/utils.lua | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 0c30f22f5..f5f3c8b1b 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -78,6 +78,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - History: added ``dfhack.getCommandHistory(history_id, history_filename)`` and ``dfhack.addCommandToHistory(history_id, history_filename, command)`` so gui scripts can access a commandline history without requiring a terminal. - ``helpdb``: database and query interface for DFHack tool help text - ``tile-material``: fix the order of declarations. The ``GetTileMat`` function now returns the material as intended (always returned nil before). Also changed the license info, with permission of the original author. +- ``utils.df_expr_to_ref()``: fixed some errors that could occur when navigating tables - ``widgets.EditField``: new ``onsubmit2`` callback attribute is called when the user hits Shift-Enter. - ``widgets.EditField``: new function: ``setCursor(position)`` sets the input cursor. - ``widgets.EditField``: new attribute: ``ignore_keys`` lets you ignore specified characters if you want to use them as hotkeys diff --git a/library/lua/utils.lua b/library/lua/utils.lua index 99ca87c6e..1b626f051 100644 --- a/library/lua/utils.lua +++ b/library/lua/utils.lua @@ -613,13 +613,15 @@ function df_expr_to_ref(expr) local obj = df_env[parts[1]] for i = 2, #parts do local key = tonumber(parts[i]) or parts[i] - local cur = obj[key] - if i == #parts and ((type(cur) ~= 'userdata') or - type(cur) == 'userdata' and getmetatable(cur) == nil) then - obj = obj:_field(key) - else - obj = obj[key] + if i == #parts then + local ok, ret = pcall(function() + return obj:_field(key) + end) + if ok then + return ret + end end + obj = obj[key] end return obj end diff --git a/test/library/utils.lua b/test/library/utils.lua index 5261ac913..2434da321 100644 --- a/test/library/utils.lua +++ b/test/library/utils.lua @@ -54,3 +54,40 @@ function test.invert_overwrite() expect.eq(i.b, 2) expect.eq(i.a, 3) end + +function test.df_expr_to_ref() + -- userdata field + expect.eq(utils.df_expr_to_ref('df.global.world.engravings'), df.global.world.engravings) + expect.eq(utils.df_expr_to_ref('df.global.world.engravings'), df.global.world:_field('engravings')) + -- primitive field + expect.eq(utils.df_expr_to_ref('df.global.world.original_save_version'), df.global.world:_field('original_save_version')) + -- table field + expect.eq(utils.df_expr_to_ref('df.global.world'), df.global.world) + expect.eq(utils.df_expr_to_ref('df.global'), df.global) + -- table + expect.eq(utils.df_expr_to_ref('df'), df) + + -- userdata object + expect.eq(utils.df_expr_to_ref('scr'), dfhack.gui.getCurViewscreen()) + + local fake_unit + mock.patch(dfhack.gui, 'getSelectedUnit', function() return fake_unit end, function() + -- lightuserdata field + fake_unit = { + null_field=df.NULL, + } + expect.eq(utils.df_expr_to_ref('unit'), fake_unit) + expect.eq(utils.df_expr_to_ref('unit.null_field'), fake_unit.null_field) + + dfhack.with_temp_object(df.unit:new(), function(u) + fake_unit = u + + -- userdata field + expect.eq(utils.df_expr_to_ref('unit.name'), fake_unit.name) + expect.eq(utils.df_expr_to_ref('unit.name'), fake_unit:_field('name')) + + -- primitive field + expect.eq(utils.df_expr_to_ref('unit.profession'), fake_unit:_field('profession')) + end) + end) +end From 2fdbcaebf5641caaae318b1a7ee29336b486df45 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 5 Sep 2022 00:08:23 -0400 Subject: [PATCH 052/121] Accept negative indices, add vector tests --- library/lua/utils.lua | 2 +- test/library/utils.lua | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/library/lua/utils.lua b/library/lua/utils.lua index 1b626f051..00e31929f 100644 --- a/library/lua/utils.lua +++ b/library/lua/utils.lua @@ -608,7 +608,7 @@ df_env = df_shortcut_env() function df_expr_to_ref(expr) expr = expr:gsub('%["(.-)"%]', function(field) return '.' .. field end) :gsub('%[\'(.-)\'%]', function(field) return '.' .. field end) - :gsub('%[(%d+)]', function(field) return '.' .. field end) + :gsub('%[(%-?%d+)%]', function(field) return '.' .. field end) local parts = expr:split('.', true) local obj = df_env[parts[1]] for i = 2, #parts do diff --git a/test/library/utils.lua b/test/library/utils.lua index 2434da321..509fab8bc 100644 --- a/test/library/utils.lua +++ b/test/library/utils.lua @@ -89,5 +89,27 @@ function test.df_expr_to_ref() -- primitive field expect.eq(utils.df_expr_to_ref('unit.profession'), fake_unit:_field('profession')) end) + + -- vector items + dfhack.with_temp_object(df.new('ptr-vector'), function(vec) + fake_unit = vec + vec:insert('#', df.global.world) + vec:insert('#', df.global.ui) + + expect.eq(utils.df_expr_to_ref('unit'), vec) + + expect.eq(utils.df_expr_to_ref('unit[0]'), utils.df_expr_to_ref('unit.0')) + expect.eq(df.reinterpret_cast(df.world, utils.df_expr_to_ref('unit[0]').value), df.global.world) + + expect.eq(utils.df_expr_to_ref('unit[1]'), utils.df_expr_to_ref('unit.1')) + expect.eq(df.reinterpret_cast(df.ui, utils.df_expr_to_ref('unit[1]').value), df.global.ui) + + expect.error_match('index out of bounds', function() utils.df_expr_to_ref('unit.2') end) + expect.error_match('index out of bounds', function() utils.df_expr_to_ref('unit[2]') end) + expect.error_match('index out of bounds', function() utils.df_expr_to_ref('unit.-1') end) + expect.error_match('index out of bounds', function() utils.df_expr_to_ref('unit[-1]') end) + + expect.error_match('not found', function() utils.df_expr_to_ref('unit.a') end) + end) end) end From 86e1a8d59c22afb84be44726835ff67a4484d080 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 7 Sep 2022 10:10:44 -0700 Subject: [PATCH 053/121] When smoothing walls, connect to doors and fgates --- docs/changelog.txt | 1 + plugins/dig-now.cpp | 26 +++++++++++++++----------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index f5f3c8b1b..8560cd4c5 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -43,6 +43,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `tags`: new built-in command to list the tool category tags and their definitions. tags associated with each tool are visible in the tool help and in the output of `ls`. ## Fixes +- `dig-now`: Fix direction of smoothed walls when adjacent to a door or floodgate - ``job.removeJob()``: ensure jobs are removed from the world list when they are canceled - `quickfort`: `Dreamfort ` blueprint set: declare the hospital zone before building the coffer; otherwise DF fails to stock the hospital with materials diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index bbcabde65..365be3313 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -424,26 +424,30 @@ static bool is_smooth_wall(MapExtras::MapCache &map, const DFCoord &pos) { && tileShape(tt) == df::tiletype_shape::WALL; } -static bool is_smooth_wall_or_door(MapExtras::MapCache &map, - const DFCoord &pos) { - if (is_smooth_wall(map, pos)) - return true; - +static bool is_connector(MapExtras::MapCache &map, const DFCoord &pos) { df::building *bld = Buildings::findAtTile(pos); - return bld && bld->getType() == df::building_type::Door; + + return bld && + (bld->getType() == df::building_type::Door || + bld->getType() == df::building_type::Floodgate); +} + +static bool is_smooth_wall_or_connector(MapExtras::MapCache &map, + const DFCoord &pos) { + return is_smooth_wall(map, pos) || is_connector(map, pos); } // adds adjacent smooth walls and doors to the given tdir static TileDirection get_adjacent_smooth_walls(MapExtras::MapCache &map, const DFCoord &pos, TileDirection tdir) { - if (is_smooth_wall_or_door(map, DFCoord(pos.x, pos.y-1, pos.z))) + if (is_smooth_wall_or_connector(map, DFCoord(pos.x, pos.y-1, pos.z))) tdir.north = 1; - if (is_smooth_wall_or_door(map, DFCoord(pos.x, pos.y+1, pos.z))) + if (is_smooth_wall_or_connector(map, DFCoord(pos.x, pos.y+1, pos.z))) tdir.south = 1; - if (is_smooth_wall_or_door(map, DFCoord(pos.x-1, pos.y, pos.z))) + if (is_smooth_wall_or_connector(map, DFCoord(pos.x-1, pos.y, pos.z))) tdir.west = 1; - if (is_smooth_wall_or_door(map, DFCoord(pos.x+1, pos.y, pos.z))) + if (is_smooth_wall_or_connector(map, DFCoord(pos.x+1, pos.y, pos.z))) tdir.east = 1; return tdir; } @@ -469,7 +473,7 @@ static bool adjust_smooth_wall_dir(MapExtras::MapCache &map, const DFCoord &pos, TileDirection tdir = BLANK_TILE_DIRECTION) { if (!is_smooth_wall(map, pos)) - return false; + return is_connector(map, pos); tdir = ensure_valid_tdir(get_adjacent_smooth_walls(map, pos, tdir)); From 040d018b8efbb86bc03c56c6a431175f36824ff0 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 7 Sep 2022 10:34:56 -0700 Subject: [PATCH 054/121] fix order of tree designation in autochop --- docs/changelog.txt | 1 + plugins/autochop.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 8560cd4c5..4f6de25f9 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -43,6 +43,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `tags`: new built-in command to list the tool category tags and their definitions. tags associated with each tool are visible in the tool help and in the output of `ls`. ## Fixes +- `autochop`: designate largest trees for chopping first, instead of the smallest - `dig-now`: Fix direction of smoothed walls when adjacent to a door or floodgate - ``job.removeJob()``: ensure jobs are removed from the world list when they are canceled - `quickfort`: `Dreamfort ` blueprint set: declare the hospital zone before building the coffer; otherwise DF fails to stock the hospital with materials diff --git a/plugins/autochop.cpp b/plugins/autochop.cpp index d96e7a972..82c40934c 100644 --- a/plugins/autochop.cpp +++ b/plugins/autochop.cpp @@ -313,7 +313,7 @@ static int do_chop_designation(bool chop, bool count_only, int *skipped = nullpt { int count = 0; int estimated_yield = get_log_count(); - multimap trees_by_size; + multimap> trees_by_size; if (skipped) { From a392a5371d6f568c25373e24d06479bfe807f2fa Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 7 Sep 2022 22:02:33 -0700 Subject: [PATCH 055/121] add keybinding for gui/workorder-details --- data/init/dfhack.keybindings.init | 3 +++ docs/changelog.txt | 1 + 2 files changed, 4 insertions(+) diff --git a/data/init/dfhack.keybindings.init b/data/init/dfhack.keybindings.init index 85d010c84..2ee706b09 100644 --- a/data/init/dfhack.keybindings.init +++ b/data/init/dfhack.keybindings.init @@ -72,6 +72,9 @@ keybinding add Alt-Q@jobmanagement/Main gui/manager-quantity # re-check manager orders keybinding add Alt-R@jobmanagement/Main workorder-recheck +# workorder detail configuration +keybinding add D@workquota_details gui/workorder-details + # view combat reports for the selected unit/corpse/spatter keybinding add Ctrl-Shift-R view-unit-reports diff --git a/docs/changelog.txt b/docs/changelog.txt index 4f6de25f9..d9a740a60 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -52,6 +52,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - Init scripts: ``dfhack.init`` and other init scripts have moved to ``dfhack-config/init/``. If you have customized your ``dfhack.init`` file and want to keep your changes, please move the part that you have customized to the new location at ``dfhack-config/init/dfhack.init``. If you do not have changes that you want to keep, do not copy anything, and the new defaults will be used automatically. - History files: ``dfhack.history``, ``tiletypes.history``, ``lua.history``, and ``liquids.history`` have moved to the ``dfhack-config`` directory. If you'd like to keep the contents of your current history files, please move them to ``dfhack-config``. - `do-job-now`: new global keybinding for boosting the priority of the jobs associated with the selected building/work order/unit/item etc.: Alt-N +- `gui/workorder-details`: new keybinding on the workorder details screen: ``D`` - `keybinding`: support backquote (\`) as a hotkey (and assign the hotkey to the new `gui/launcher` interface) - `ls`: can now filter tools by substring or tag. note that dev scripts are hidden by default. pass the ``--dev`` option to show them. - `manipulator`: add a library of useful default professions From 4adcbe174652f3f4d2c94c138db4f1c80711cd00 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Fri, 9 Sep 2022 07:25:12 +0000 Subject: [PATCH 056/121] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index e6e25ea37..ca280f26b 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit e6e25ea3720489fbf15e266c3c604980b35d499f +Subproject commit ca280f26b358cbf1ffffd27efe1b67a40fb4db35 From 122374f018e45f01c740afe8ffbf03e6070196e6 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Sat, 10 Sep 2022 07:19:20 +0000 Subject: [PATCH 057/121] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index ca280f26b..a59356e0d 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit ca280f26b358cbf1ffffd27efe1b67a40fb4db35 +Subproject commit a59356e0d79072e13df7f50e86df1ecd5645a041 From 2733ce7684aa5c897f45a62470c658728edcd264 Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 8 Sep 2022 21:57:29 -0700 Subject: [PATCH 058/121] update workflow summary to match the gui version --- docs/plugins/workflow.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plugins/workflow.rst b/docs/plugins/workflow.rst index 6bf89008b..9013ba189 100644 --- a/docs/plugins/workflow.rst +++ b/docs/plugins/workflow.rst @@ -2,7 +2,7 @@ workflow ======== .. dfhack-tool:: - :summary: Manage repeat jobs according to stock levels. + :summary: Manage automated item production rules. :tags: fort auto jobs .. dfhack-command:: fix-job-postings From cae2bca0a7487195d0381c9bf4013d0fcaf0773f Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 10 Sep 2022 09:49:22 -0700 Subject: [PATCH 059/121] add dfhack.screen.hideGuard --- docs/Lua API.rst | 6 ++++++ library/LuaApi.cpp | 16 ++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 942ccfa8f..71094414f 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1992,6 +1992,12 @@ Functions: Returns: *tile, tile_grayscale*, or *nil* if not found. The values can then be used for the *tile* field of *pen* structures. +* ``dfhack.screen.hideGuard(screen,callback[,args...])`` + + Removes screen from the viewscreen stack, calls the callback (with optional + supplied arguments), and then restores the screen on the top of the viewscreen + stack. + * ``dfhack.screen.clear()`` Fills the screen with blank background. diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index d04da21ec..70cb01dfc 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -2370,6 +2370,21 @@ static int screen_findGraphicsTile(lua_State *L) } } +static int screen_hideGuard(lua_State *L) { + df::viewscreen *screen = dfhack_lua_viewscreen::get_pointer(L, 1, false); + luaL_checktype(L, 2, LUA_TFUNCTION); + + // remove screen from the stack so it doesn't get returned as an output + lua_remove(L, 1); + + Screen::Hide hideGuard(screen, Screen::Hide::RESTORE_AT_TOP); + + int nargs = lua_gettop(L) - 1; + lua_call(L, nargs, LUA_MULTRET); + + return lua_gettop(L); +} + namespace { int screen_show(lua_State *L) @@ -2472,6 +2487,7 @@ static const luaL_Reg dfhack_screen_funcs[] = { { "paintString", screen_paintString }, { "fillRect", screen_fillRect }, { "findGraphicsTile", screen_findGraphicsTile }, + CWRAP(hideGuard, screen_hideGuard), CWRAP(show, screen_show), CWRAP(dismiss, screen_dismiss), CWRAP(isDismissed, screen_isDismissed), From 3c99a7214f2f84ff767604ef805b3451ebb0de64 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 10 Sep 2022 10:10:28 -0700 Subject: [PATCH 060/121] ensure params are strings when invoking scripts --- library/lua/dfhack.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 8af77e2e4..4a46040af 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -789,7 +789,11 @@ function dfhack.run_script_with_env(envVars, name, flags, ...) end scripts[file].env = env scripts[file].run = script_code - return script_code(...), env + local args = {...} + for i,v in ipairs(args) do + args[i] = tostring(v) -- ensure passed parameters are strings + end + return script_code(table.unpack(args)), env end function dfhack.current_script_name() From 82e954692e156b3588dd5e930d606900a66b2c63 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 10 Sep 2022 10:15:08 -0700 Subject: [PATCH 061/121] update changelog --- docs/changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index d9a740a60..7cf852228 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -44,6 +44,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Fixes - `autochop`: designate largest trees for chopping first, instead of the smallest +- ``dfhack.run_script``: ensure the arguments passed to scripts are always strings. This allows other scripts to call ``run_script`` with numeric args and it won't break parameter parsing. - `dig-now`: Fix direction of smoothed walls when adjacent to a door or floodgate - ``job.removeJob()``: ensure jobs are removed from the world list when they are canceled - `quickfort`: `Dreamfort ` blueprint set: declare the hospital zone before building the coffer; otherwise DF fails to stock the hospital with materials From bd51d9c4551c0c2a581a3bd4d18747c15c31dc11 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 10 Sep 2022 15:34:17 -0400 Subject: [PATCH 062/121] Changelog entry for hideGuard --- docs/changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index 7cf852228..3ef25f5dd 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -80,6 +80,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Lua - History: added ``dfhack.getCommandHistory(history_id, history_filename)`` and ``dfhack.addCommandToHistory(history_id, history_filename, command)`` so gui scripts can access a commandline history without requiring a terminal. +- Added ``dfhack.screen.hideGuard()``: exposes the C++ ``Screen::Hide`` to Lua - ``helpdb``: database and query interface for DFHack tool help text - ``tile-material``: fix the order of declarations. The ``GetTileMat`` function now returns the material as intended (always returned nil before). Also changed the license info, with permission of the original author. - ``utils.df_expr_to_ref()``: fixed some errors that could occur when navigating tables From 55a8db4efb4e31c991931aabeda4bb8ae330fe07 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Sun, 11 Sep 2022 07:18:50 +0000 Subject: [PATCH 063/121] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index a59356e0d..773a6ec53 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit a59356e0d79072e13df7f50e86df1ecd5645a041 +Subproject commit 773a6ec53ec0445d5039b68204bfb1ce60b39603 From 23994d4f4c59d32a10b952d1419a4e92152394fe Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 11 Sep 2022 13:41:20 -0700 Subject: [PATCH 064/121] dynamically wrap helpdb long help text (default 80) --- conf.py | 6 +++++- docs/Lua API.rst | 6 ++++-- library/lua/helpdb.lua | 38 +++++++++++++++++++++++++++++++++++--- 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/conf.py b/conf.py index 924da1577..68d11f0f9 100644 --- a/conf.py +++ b/conf.py @@ -347,6 +347,10 @@ latex_toplevel_sectioning = 'part' from sphinx.writers import text -text.MAXWIDTH = 52 +# this value is arbitrary. it just needs to be bigger than the number of +# characters in the longest paragraph in the DFHack docs +text.MAXWIDTH = 1000000000 +# this is the order that section headers will use the characters for underlines +# they are in the order of (subjective) text-mode readability text_sectionchars = '=-~`+"*' diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 71094414f..cc80dd92d 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -3114,9 +3114,11 @@ Each entry has several properties associated with it: Returns the short (~54 character) description for the given entry. -* ``helpdb.get_entry_long_help(entry)`` +* ``helpdb.get_entry_long_help(entry[, width])`` - Returns the full help text for the given entry. + Returns the full help text for the given entry. If ``width`` is specified, the + text will be wrapped at that width, preserving block indents. The wrap width + defaults to 80. * ``helpdb.get_entry_tags(entry)`` diff --git a/library/lua/helpdb.lua b/library/lua/helpdb.lua index 2f41bbb2c..805b56db9 100644 --- a/library/lua/helpdb.lua +++ b/library/lua/helpdb.lua @@ -432,6 +432,37 @@ local function ensure_db() index_tags() end +local function parse_blocks(text) + local blocks = {} + for line in text:gmatch('[^\n]*') do + local _,indent = line:find('^ *') + table.insert(blocks, {line=line:trim(), indent=indent}) + end + return blocks +end + +local function format_block(line, indent, width) + local wrapped = line:wrap(width - indent) + if indent == 0 then return wrapped end + local padding = (' '):rep(indent) + local indented_lines = {} + for line in wrapped:gmatch('[^\n]*') do + table.insert(indented_lines, padding .. line) + end + return table.concat(indented_lines, '\n') +end + +-- wraps the unwrapped source help at the specified width, preserving block +-- indents +local function rewrap(text, width) + local formatted_blocks = {} + for _,block in ipairs(parse_blocks(text)) do + table.insert(formatted_blocks, + format_block(block.line, block.indent, width)) + end + return table.concat(formatted_blocks, '\n') +end + --------------------------------------------------------------------------- -- get API --------------------------------------------------------------------------- @@ -482,9 +513,10 @@ function get_entry_short_help(entry) return get_db_property(entry, 'short_help') end --- returns the full help documentation associated with the entry -function get_entry_long_help(entry) - return get_db_property(entry, 'long_help') +-- returns the full help documentation associated with the entry, optionally +-- wrapped to the specified width (80 if not specified). +function get_entry_long_help(entry, width) + return rewrap(get_db_property(entry, 'long_help'), width or 80) end -- returns the set of tags associated with the entry From 49798f6412e6f3e1181efe7ad5942365ced9aaad Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 11 Sep 2022 13:51:57 -0700 Subject: [PATCH 065/121] add unit test for wrapping --- test/library/helpdb.lua | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/test/library/helpdb.lua b/test/library/helpdb.lua index 6c2ef2e9b..96dcbbce0 100644 --- a/test/library/helpdb.lua +++ b/test/library/helpdb.lua @@ -398,8 +398,25 @@ function test.get_entry_short_help() end function test.get_entry_long_help() + local expected = [[ +basic +***** + +**Tags:** map + +**Command:** +"basic" + +Documented +basic. + +Documented +full help. + ]] + expect.eq(expected, h.get_entry_long_help('basic', 13)) + -- long help for plugins/commands that have doc files should match the - -- contents of those files exactly + -- contents of those files exactly (test data is already wrapped) expect.eq(files['hack/docs/docs/tools/hascommands.txt'], h.get_entry_long_help('hascommands')) expect.eq(files['hack/docs/docs/tools/hascommands.txt'], From 23111587923cc61fa2de17c8e7be2f2ccaa2e140 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 11 Sep 2022 14:06:21 -0700 Subject: [PATCH 066/121] fix reference to old ls param in Lua API docs also add another example to the ls example commands --- docs/Lua API.rst | 6 +++--- docs/builtins/ls.rst | 12 +++++++----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 71094414f..3742b0851 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -928,9 +928,9 @@ can be omitted. The following examples are equivalent:: - dfhack.run_command({'ls', '-a'}) - dfhack.run_command('ls', '-a') - dfhack.run_command('ls -a') -- not recommended + dfhack.run_command({'ls', 'quick'}) + dfhack.run_command('ls', 'quick') + dfhack.run_command('ls quick') -- not recommended * ``dfhack.run_command_silent(command[, ...])`` diff --git a/docs/builtins/ls.rst b/docs/builtins/ls.rst index 6b21e4b53..7305a0256 100644 --- a/docs/builtins/ls.rst +++ b/docs/builtins/ls.rst @@ -25,11 +25,13 @@ Usage Examples -------- -- ``ls adventure`` - Lists all commands with the ``adventure`` tag. -- ``ls --dev trigger`` - Lists all commands, including developer and modding commands, that match the - substring "trigger" +``ls quick`` + List all commands that match the substring "quick". +``ls adventure`` + List all commands with the ``adventure`` tag. +``ls --dev trigger`` + List all commands, including developer and modding commands, that match the + substring "trigger". Options ------- From f71f034d1d4bcd6be27d106d2c7365117e0029ba Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 11 Sep 2022 17:58:10 -0700 Subject: [PATCH 067/121] show full scrollbars instead of just scroll icons --- docs/Lua API.rst | 7 ++-- docs/changelog.txt | 1 + library/lua/gui/widgets.lua | 78 +++++++++++++++++++++++++++---------- 3 files changed, 62 insertions(+), 24 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index a84458ee8..fee502b80 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -4039,13 +4039,12 @@ It has the following attributes: keys to the number of lines to scroll as positive or negative integers or one of the keywords supported by the ``scroll`` method. The default is up/down arrows scrolling by one line and page up/down scrolling by one page. -:show_scroll_icons: Controls scroll icons' behaviour: ``false`` for no icons, ``'right'`` or ``'left'`` for +:show_scrollbar: Controls scrollbar display: ``false`` for no scrollbar, ``'right'`` or ``'left'`` for icons next to the text in an additional column (``frame_inset`` is adjusted to have ``.r`` or ``.l`` greater than ``0``), ``nil`` same as ``'right'`` but changes ``frame_inset`` only if a scroll icon is actually necessary (if ``getTextHeight()`` is greater than ``frame_body.height``). Default is ``nil``. -:up_arrow_icon: The symbol for scroll up arrow. Default is ``string.char(24)`` (``↑``). -:down_arrow_icon: The symbol for scroll down arrow. Default is ``string.char(25)`` (``↓``). -:scroll_icon_pen: Specifies the pen for scroll icons. Default is ``COLOR_LIGHTCYAN``. +:scrollbar_fg: Specifies the pen for the scroll icons and the active part of the bar. Default is ``COLOR_LIGHTGREEN`` (the same as the native DF help screens). +:scrollbar_bg: Specifies the pen for the background part of the scrollbar. Default is ``COLOR_CYAN`` (the same as the native DF help screens). The text itself is represented as a complex structure, and passed to the object via the ``text`` argument of the constructor, or via diff --git a/docs/changelog.txt b/docs/changelog.txt index 3ef25f5dd..5abae3dec 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -64,6 +64,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `seedwatch`: ``seedwatch all`` now adds all plants with seeds to the watchlist, not just the "basic" crops. - UX: You can now move the cursor around in DFHack text fields in ``gui/`` scripts (e.g. `gui/blueprint`, `gui/quickfort`, or `gui/gm-editor`). You can move the cursor by clicking where you want it to go with the mouse or using the Left/Right arrow keys. Ctrl+Left/Right will move one word at a time, and Alt+Left/Right will move to the beginning/end of the text. - UX: You can now click on the hotkey hint text in many ``gui/`` script windows to activate the hotkey, like a button. Not all scripts have been updated to use the clickable widget yet, but you can try it in `gui/blueprint` or `gui/quickfort`. +- UX: Label widget scroll icons are replaced with scrollbars that represent the percentage of text on the screen and move with the position of the visible text, just like web browser scrollbars. - `quickfort`: `Dreamfort ` blueprint set improvements: set traffic designations to encourage dwarves to eat cooked food instead of raw ingredients ## Documentation diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index d05749417..76683b45a 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -544,12 +544,10 @@ Label.ATTRS{ auto_width = false, on_click = DEFAULT_NIL, on_rclick = DEFAULT_NIL, - -- scroll_keys = STANDARDSCROLL, - show_scroll_icons = DEFAULT_NIL, -- DEFAULT_NIL, 'right', 'left', false - up_arrow_icon = string.char(24), - down_arrow_icon = string.char(25), - scroll_icon_pen = COLOR_LIGHTCYAN, + show_scrollbar = DEFAULT_NIL, -- DEFAULT_NIL, 'right', 'left', false + scrollbar_fg = COLOR_LIGHTGREEN, + scrollbar_bg = COLOR_CYAN } function Label:init(args) @@ -573,16 +571,16 @@ function Label:setText(text) end function Label:update_scroll_inset() - if self.show_scroll_icons == nil then - self._show_scroll_icons = self:getTextHeight() > self.frame_body.height and 'right' or false + if self.show_scrollbar == nil then + self._show_scrollbar = self:getTextHeight() > self.frame_body.height and 'right' or false else - self._show_scroll_icons = self.show_scroll_icons + self._show_scrollbar = self.show_scrollbar end - if self._show_scroll_icons then - -- here self._show_scroll_icons can only be either + if self._show_scrollbar then + -- here self._show_scrollbar can only be either -- 'left' or any true value which we interpret as right local l,t,r,b = gui.parse_inset(self.frame_inset) - if self._show_scroll_icons == 'left' and l <= 0 then + if self._show_scrollbar == 'left' and l <= 0 then l = 1 elseif r <= 0 then r = 1 @@ -591,14 +589,54 @@ function Label:update_scroll_inset() end end -function Label:render_scroll_icons(dc, x, y1, y2) - if self.start_line_num ~= 1 then - dc:seek(x, y1):char(self.up_arrow_icon, self.scroll_icon_pen) +-- the position is the number of tiles of empty space above the top of the +-- scrollbar, and the height is the number of tiles the scrollbar should occupy +-- to represent the percentage of text that is on the screen. +local function get_scrollbar_pos_and_height(label) + local first_visible_line = label.start_line_num + local text_height = label:getTextHeight() + local last_visible_line = first_visible_line + label.frame_body.height - 1 + local scrollbar_body_height = label.frame_body.height - 2 + local displayed_lines = last_visible_line - first_visible_line + + local height = math.min(scrollbar_body_height - 1, + math.ceil((displayed_lines-1) * scrollbar_body_height / text_height)) + + local max_pos = scrollbar_body_height - height + local pos = math.ceil(((first_visible_line-1) * max_pos) / + (text_height - label.frame_body.height)) + + return pos, height +end + +local UP_ARROW_CHAR = string.char(24) +local DOWN_ARROW_CHAR = string.char(25) +local NO_ARROW_CHAR = string.char(32) +local BAR_CHAR = string.char(7) +local BAR_BG_CHAR = string.char(179) + +function Label:render_scrollbar(dc, x, y1, y2) + -- render up arrow if we're not at the top + dc:seek(x, y1):char( + self.start_line_num == 1 and NO_ARROW_CHAR or UP_ARROW_CHAR, + self.scrollbar_fg, self.scrollbar_bg) + -- render scrollbar body + local pos, height = get_scrollbar_pos_and_height(self) + local starty = y1 + pos + 1 + local endy = y1 + pos + height + for y=y1+1,y2-1 do + if y >= starty and y <= endy then + dc:seek(x, y):char(BAR_CHAR, self.scrollbar_fg) + else + dc:seek(x, y):char(BAR_BG_CHAR, self.scrollbar_bg) + end end + -- render down arrow if we're not at the bottom local last_visible_line = self.start_line_num + self.frame_body.height - 1 - if last_visible_line < self:getTextHeight() then - dc:seek(x, y2):char(self.down_arrow_icon, self.scroll_icon_pen) - end + dc:seek(x, y2):char( + last_visible_line >= self:getTextHeight() and + NO_ARROW_CHAR or DOWN_ARROW_CHAR, + self.scrollbar_fg, self.scrollbar_bg) end function Label:computeFrame(parent_rect) @@ -644,13 +682,13 @@ function Label:onRenderBody(dc) end function Label:onRenderFrame(dc, rect) - if self._show_scroll_icons + if self._show_scrollbar and self:getTextHeight() > self.frame_body.height then - local x = self._show_scroll_icons == 'left' + local x = self._show_scrollbar == 'left' and self.frame_body.x1-dc.x1-1 or self.frame_body.x2-dc.x1+1 - self:render_scroll_icons(dc, + self:render_scrollbar(dc, x, self.frame_body.y1-dc.y1, self.frame_body.y2-dc.y1 From bcf1b5ddace46da290665c9a8d15ce280474b14e Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 11 Sep 2022 18:08:00 -0700 Subject: [PATCH 068/121] update tests --- test/library/gui/widgets.Label.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/library/gui/widgets.Label.lua b/test/library/gui/widgets.Label.lua index 6b0097d1e..c43b5e886 100644 --- a/test/library/gui/widgets.Label.lua +++ b/test/library/gui/widgets.Label.lua @@ -29,7 +29,7 @@ function test.correct_frame_body_with_scroll_icons() end local o = fs{} - expect.eq(o.subviews.text.frame_body.width, 9, "Label's frame_body.x2 and .width should be one smaller because of show_scroll_icons.") + expect.eq(o.subviews.text.frame_body.width, 9, "Label's frame_body.x2 and .width should be one smaller because of show_scrollbar.") end function test.correct_frame_body_with_few_text_lines() @@ -50,10 +50,10 @@ function test.correct_frame_body_with_few_text_lines() end local o = fs{} - expect.eq(o.subviews.text.frame_body.width, 10, "Label's frame_body.x2 and .width should not change with show_scroll_icons = false.") + expect.eq(o.subviews.text.frame_body.width, 10, "Label's frame_body.x2 and .width should not change with show_scrollbar = false.") end -function test.correct_frame_body_without_show_scroll_icons() +function test.correct_frame_body_without_show_scrollbar() local t = {} for i = 1, 12 do t[#t+1] = tostring(i) @@ -66,13 +66,13 @@ function test.correct_frame_body_without_show_scroll_icons() view_id = 'text', frame_inset = 0, text = t, - show_scroll_icons = false, + show_scrollbar = false, }, } end local o = fs{} - expect.eq(o.subviews.text.frame_body.width, 10, "Label's frame_body.x2 and .width should not change with show_scroll_icons = false.") + expect.eq(o.subviews.text.frame_body.width, 10, "Label's frame_body.x2 and .width should not change with show_scrollbar = false.") end function test.scroll() From e2fbade2195a94e350610e254a2ca4d051cc35e0 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 11 Sep 2022 18:56:43 -0700 Subject: [PATCH 069/121] fix scrollbar display when all text is visible also align behavior with documentation when 'left' or 'right' is explicitly specified (i.e. always show the scrollbar) --- library/lua/gui/widgets.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 76683b45a..e85ac2d3c 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -599,8 +599,8 @@ local function get_scrollbar_pos_and_height(label) local scrollbar_body_height = label.frame_body.height - 2 local displayed_lines = last_visible_line - first_visible_line - local height = math.min(scrollbar_body_height - 1, - math.ceil((displayed_lines-1) * scrollbar_body_height / text_height)) + local height = math.floor(((displayed_lines-1) * scrollbar_body_height) / + text_height) local max_pos = scrollbar_body_height - height local pos = math.ceil(((first_visible_line-1) * max_pos) / @@ -683,7 +683,6 @@ end function Label:onRenderFrame(dc, rect) if self._show_scrollbar - and self:getTextHeight() > self.frame_body.height then local x = self._show_scrollbar == 'left' and self.frame_body.x1-dc.x1-1 From d4914e7511a8cc48e09d55515aec1ab8af84991a Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 11 Sep 2022 19:33:01 -0700 Subject: [PATCH 070/121] implement mouse click reactions for scrollbar --- docs/Lua API.rst | 5 +++++ library/lua/gui/widgets.lua | 39 +++++++++++++++++++++++++++++++++---- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index fee502b80..b97c1b314 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -4046,6 +4046,11 @@ It has the following attributes: :scrollbar_fg: Specifies the pen for the scroll icons and the active part of the bar. Default is ``COLOR_LIGHTGREEN`` (the same as the native DF help screens). :scrollbar_bg: Specifies the pen for the background part of the scrollbar. Default is ``COLOR_CYAN`` (the same as the native DF help screens). +If the scrollbar is shown, it will react to mouse clicks on the scrollbar itself. +Clicking on the arrows at the top or the bottom will scroll by one line, and +clicking on the unfilled portion of the scrollbar will scroll by a half page in +that direction. + The text itself is represented as a complex structure, and passed to the object via the ``text`` argument of the constructor, or via the ``setText`` method, as one of: diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index e85ac2d3c..d81fbf6b9 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -682,8 +682,7 @@ function Label:onRenderBody(dc) end function Label:onRenderFrame(dc, rect) - if self._show_scrollbar - then + if self._show_scrollbar then local x = self._show_scrollbar == 'left' and self.frame_body.x1-dc.x1-1 or self.frame_body.x2-dc.x1+1 @@ -695,7 +694,35 @@ function Label:onRenderFrame(dc, rect) end end +function Label:click_scrollbar() + if not self._show_scrollbar then return end + local rect = self.frame_body + local x, y = dscreen.getMousePos() + + if self._show_scrollbar == 'left' and x ~= rect.x1-1 or x ~= rect.x2+1 then + return + end + if y < rect.y1 or y > rect.y2 then + return + end + + if y == rect.y1 then + return -1 + elseif y == rect.y2 then + return 1 + else + local pos, height = get_scrollbar_pos_and_height(self) + if y < rect.y1 + pos then + return '-halfpage' + elseif y > rect.y1 + pos + height then + return '+halfpage' + end + end + return nil +end + function Label:scroll(nlines) + if not nlines then return end if type(nlines) == 'string' then if nlines == '+page' then nlines = self.frame_body.height @@ -713,12 +740,16 @@ function Label:scroll(nlines) n = math.min(n, self:getTextHeight() - self.frame_body.height + 1) n = math.max(n, 1) self.start_line_num = n + return nlines end function Label:onInput(keys) if is_disabled(self) then return false end - if keys._MOUSE_L_DOWN and self:getMousePos() and self.on_click then - self:on_click() + if keys._MOUSE_L_DOWN then + if not self:scroll(self:click_scrollbar()) and + self:getMousePos() and self.on_click then + self:on_click() + end end if keys._MOUSE_R_DOWN and self:getMousePos() and self.on_rclick then self:on_rclick() From 7b63377cd82013132437742c7286821bf8605efa Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Mon, 12 Sep 2022 02:38:01 +0000 Subject: [PATCH 071/121] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 773a6ec53..8c89b0891 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 773a6ec53ec0445d5039b68204bfb1ce60b39603 +Subproject commit 8c89b08916a00b85d9bb476bf1855dcd8642a0f7 From 253a1a80ada0928d0ac660ec38b545c3f4f2bc91 Mon Sep 17 00:00:00 2001 From: Myk Date: Sun, 11 Sep 2022 23:02:04 -0700 Subject: [PATCH 072/121] add modding guide to guides index --- docs/guides/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/guides/index.rst b/docs/guides/index.rst index d0125921f..161b1bb68 100644 --- a/docs/guides/index.rst +++ b/docs/guides/index.rst @@ -8,6 +8,7 @@ These pages are detailed guides covering DFHack tools. :maxdepth: 1 /docs/guides/examples-guide + /docs/guides/modding-guide /docs/guides/quickfort-library-guide /docs/guides/quickfort-user-guide /docs/guides/quickfort-alias-guide From 540a2a9fa75218b1f931f192277777b891d0ac74 Mon Sep 17 00:00:00 2001 From: Myk Date: Mon, 12 Sep 2022 00:14:03 -0700 Subject: [PATCH 073/121] editing pass up through The structure of the game --- docs/guides/modding-guide.rst | 79 ++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 34 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index e35422aef..45f922b10 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -3,51 +3,61 @@ DFHack modding guide ==================== -What is a mod/script? ---------------------- +What is the difference between a script and a mod? +-------------------------------------------------- A script is a single file that can be run as a command in DFHack, like something that modifies or displays game data on request. A mod is something you install -to get persistent behavioural changes in the game and/or add new content. DFHack -mods contain and use scripts as well as often having a raw mod component. +to get persistent behavioural changes in the game and/or add new content. Mods +can contain and use scripts in addition to (or instead of) modifications to the +DF game raws. DFHack scripts are written in Lua. If you don't already know Lua, there's a -great primer at https://www.lua.org/pil/1.html. +great primer at `lua.org `__. -Why not just use raw modding? ------------------------------ +Why not just mod the raws? +-------------------------- -For many things it's either completely and only (sensibly) doable in raws or -completely and only doable with DFHack. For mods where DFHack is an alternative -and not the only option, it's much less hacky, easier to maintain, and easier to -extend, and is not prone to side-effects. A great example is adding a syndrome -when a reaction is performed requiring an exploding boulder in raws but having -dedicated tools for it if you use DFHack. Many things will require a mix of raw -modding and DFHack. +It depends on what you want to do. Some mods *are* better to do in just the raws. +You don't need DFHack to add a new race or modify attributes, for example. However, +DFHack scripts can do many things that you just can't do in the raws, like make a +creature that trails smoke. Some things *could* be done in the raws, but writing a +script is less hacky, easier to maintain, easier to extend, and is not prone to +side-effects. A great example is adding a syndrome when a reaction is performed. +If done in the raws, you have to create an exploding boulder to effect the syndrome. +DFHack scripts can add the syndrome directly and with much more flexibility. In the +end, complex mods will likely require a mix of raw modding and DFHack scripting. A mod-maker's development environment ------------------------------------- -Scripts can be run from a world's ``raw/scripts/`` directory, and (configurably) -are run by default from ``hack/scripts/``. Scripts in ``raw/init.d/`` are -automatically run on world load. Scripts within the raws are a component for -more advanced mods. +While you're writing your mod, you need a place to store your in-development scripts +that will: -A script is run by writing its path and name from a script path folder without -the file extension into a DFHack command prompt (in-game or the external one). -E.g. ``gui/gm-editor`` for ``hack/scripts/gui/gm-editor.lua``. +- be directly runnable by DFHack +- not get lost when you upgrade DFHack -You can make all your scripts in ``hack/scripts/``, but this is not recommended -as it makes things much harder to maintain each update. It's recommended to make -a folder with a name like ``own-scripts`` and add it to -``dfhack-config/script-paths.txt``. You could also make a folder for external -installed scripts from the internet that are not in ``hack/scripts/``. You can -prepend your script paths entries with a ``+`` so that they take precedence over -other folders. +The recommended approach is to create a directory somewhere outside of your DF +installation (let's call it "/path/to/own-scripts") and do all your script +development in there. -If your mod is installed into ``raw/scripts/`` be aware that the copies of the -scripts in ``data/save/*/raw/`` are checked first and will run instead of any -changes you make to an in-development copy outside of a raw folder. +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:: + + +/path/to/own-scripts + +Then that directory will be searched when you run DFHack commands from inside the +game. The ``+`` at the front of the path means to search that directory 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 +older copies that you may have installed in a DF directory. + +For scripts with the same name, the `order of precedence ` will be: + +1. ``own-scripts/`` +2. ``data/save/*/raw/scripts/`` +3. ``raw/scripts/`` +4. ``hack/scripts/`` The structure of the game ------------------------- @@ -55,7 +65,8 @@ The structure of the game "The game" is in the global variable `df `. The game's memory can be found in ``df.global``, containing things like the list of all items, whether to reindex pathfinding, et cetera. Also relevant to us in ``df`` are the various -types found in the game, e.g. ``df.pronoun_type`` which we will be using. +types found in the game, e.g. ``df.pronoun_type`` which we will be using in this +guide. We'll explore more of the game structures `below `_. Your first script ----------------- @@ -88,8 +99,8 @@ with one of the strings, will yield its corresponding number. So: :: Simple. Save this as a Lua file in your own scripts directory and run it as shown before when focused on a unit one way or another. -Getting used to gm-editor and df-structures exploration -------------------------------------------------------- +Exploring DF structures +----------------------- 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 From dde56040c3142ed561d37a620e1b46149f38c8f1 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Mon, 12 Sep 2022 07:39:33 +0000 Subject: [PATCH 074/121] Auto-update submodules library/xml: master scripts: master --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index dc118c5e9..f5fab13fb 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit dc118c5e90aea6181a290e4bbf40e7f2974fb053 +Subproject commit f5fab13fb652dd953e9d59e8deca032b26131208 diff --git a/scripts b/scripts index 8c89b0891..d0b484021 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 8c89b08916a00b85d9bb476bf1855dcd8642a0f7 +Subproject commit d0b484021553574c0eadfb49b30b3007856683b7 From 0e30160c3ce50e76711efbf77f53870804d48d49 Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 12 Sep 2022 14:15:46 -0700 Subject: [PATCH 075/121] update documentation documentation --- docs/Documentation.rst | 107 ++++++++++++++++++++--------------------- docs/Lua API.rst | 38 ++++++--------- 2 files changed, 68 insertions(+), 77 deletions(-) diff --git a/docs/Documentation.rst b/docs/Documentation.rst index 1d30e72ff..4109a81c7 100644 --- a/docs/Documentation.rst +++ b/docs/Documentation.rst @@ -8,7 +8,7 @@ DFHack Documentation System DFHack documentation, like the file you are reading now, is created as a set of ``.rst`` files in `reStructuredText (reST) `_ format. This is a documentation format common in the Python community. It is very -similar in concept - and in syntax - to Markdown, as found on GitHub and many other +similar in concept -- and in syntax -- to Markdown, as found on GitHub and many other places. However it is more advanced than Markdown, with more features available when compiled to HTML, such as automatic tables of contents, cross-linking, special external links (forum, wiki, etc) and more. The documentation is compiled by a @@ -23,11 +23,13 @@ if you're changing them and want to see the changes reflected in your game. You can also build the docs if you just want a local HTML- or text-rendered copy, though you can always read the `online version `_ too. +The active development version of the documentation is tagged with ``latest`` and +is available `here `_ -(Note that even if you do want a local copy, it is certainly not necessary to +Note that even if you do want a local copy, it is certainly not necessary to compile the documentation in order to read it. Like Markdown, reST documents are designed to be just as readable in a plain-text editor as they are in HTML format. -The main thing you lose in plain text format is hyperlinking.) +The main thing you lose in plain text format is hyperlinking. .. contents:: Contents :local: @@ -42,19 +44,17 @@ location in ``hack/docs`` under the DF directory. When writing documentation, remember that everything should be documented! If it's not clear *where* a particular thing should be documented, ask on Discord or in the DFHack thread on Bay12 -- you'll not only be getting help, you'll also be providing valuable -feedback that makes it easier for future contributers to find documentation on how to +feedback that makes it easier for future contributors to find documentation on how to write the documentation! -Try to keep lines within 80-100 characters, so it's readable in plain text -in the terminal - Sphinx (our documentation system) will make sure -paragraphs flow. +Try to keep lines within 80-100 characters so it's readable in plain text in the +terminal - Sphinx (our documentation system) will make sure paragraphs flow. Short descriptions ------------------ -Each command that a user can run, as well as every plugin that can be enabled for some -lasting effect, needs to have a short (~54 character) descriptive string associated with -it. This description text is: +Each command that a user can run -- as well as every plugin -- needs to have a +short (~54 character) descriptive string associated with it. This description text is: - used in-game by the `ls` command and DFHack UI screens that list commands - used in the generated index entries in the HTML docs @@ -114,10 +114,9 @@ in a file named ``docs/gui/foobar.rst`` in the scripts repo. Similarly, a plugin ``foobaz`` should be documented in a file named ``docs/plugins/foobaz.rst`` in the dfhack repo. For plugins, all commands provided by that plugin should be documented in that same file. -Short descriptions (the ~54 character short help) are taken from the first "sentence" of -the help text for scripts and plugins that can be enabled. This means that the help should -begin with a sentence fragment that begins with a capital letter and ends in a full stop -(``.``). Please make this brief but descriptive! +Short descriptions (the ~54 character short help) for scripts and plugins are taken from +the ``summary`` attribute of the ``dfhack-tool`` directive that each tool help document must +have (see the `Header format`_ section below). Please make this brief but descriptive! Short descriptions for commands provided by plugins are taken from the ``description`` parameter passed to the ``PluginCommand`` constructor used when the command is registered @@ -127,11 +126,11 @@ Header format ------------- The docs **must** begin with a heading which exactly matches the script or plugin name, underlined -with ``=====`` to the same length. This should be followed by a ``.. dfhack-tool:`` directive with +with ``=====`` to the same length. This must be followed by a ``.. dfhack-tool:`` directive with at least the following parameters: * ``:summary:`` - a short, single-sentence description of the tool -* ``:tags:`` - a space-separated list of tags that apply to the tool +* ``:tags:`` - a space-separated list of `tags ` that apply to the tool By default, ``dfhack-tool`` generates both a description of a tool and a command with the same name. For tools (specifically plugins) that do not provide exactly @@ -231,6 +230,7 @@ angle brackets (``<`` and ``>``). Optional elements are enclosed in square brack If the command takes an arbitrary number of elements, use ``...``, for example:: prioritize [] [ ...] + quickfort [,...] [,...] [] Examples -------- @@ -240,11 +240,11 @@ Otherwise, please consider adding a section that shows some real, practical usag many users, this will be the **only** section they will read. It is so important that it is a good idea to include the ``Examples`` section **before** you describe any extended options your command might take. Write examples for what you expect the popular use cases will be. Also be sure to write -examples showing specific, practical values being used for any parameter that takes a value. +examples showing specific, practical values being used for any parameter that takes a value or has +tricky formatting. -Examples should go in their own subheading with a single dash underline (``--------``). The examples -themselves should be organized in a list, the same as in option 2 for Usage above. Here is an -example Examples section:: +Examples should go in their own subheading. The examples themselves should be organized as in +option 2 for Usage above. Here is an example ``Examples`` section:: Examples -------- @@ -258,7 +258,7 @@ example Examples section:: Options ------- -The options header should follow the examples, with each option in the same list format as the +The options header should follow the examples, with each option in the same format as the examples:: Options @@ -285,7 +285,8 @@ scripts and plugins can use a different mechanism to at least make their help te in-game. Note that since help text for external scripts and plugins is not rendered by Sphinx, -it should be written in plain text. Any reStructuredText markup will not be processed. +it should be written in plain text. Any reStructuredText markup will not be processed and, +if present, will be shown verbatim to the player (which is probably not what you want). For external scripts, the short description comes from a comment on the first line (the comment marker and extra whitespace is stripped). For Lua, this would look like: @@ -310,27 +311,33 @@ entire script header:: -- [====[ gui/adv-inventory ================= - Tags: adventure, items + + Tags: adventure | items Allows you to quickly move items between containers. This includes yourself and any followers you have. - Usage: + Usage + ----- gui/adv-inventory [] - Examples: + Examples + -------- gui/adv-inventory Opens the GUI with nothing preselected + gui/adv-inventory take-all Opens the GUI with all container items already selected and ready to move into the adventurer's inventory. - Options: + Options + ------- take-all Starts the GUI with container items pre-selected + give-all Starts the GUI with your own items pre-selected ]====] @@ -347,10 +354,10 @@ Required dependencies .. highlight:: shell In order to build the documentation, you must have Python with Sphinx -version |sphinx_min_version| or later. Python 3 is recommended. +version |sphinx_min_version| or later and Python 3. When installing Sphinx from OS package managers, be aware that there is -another program called Sphinx, completely unrelated to documentation management. +another program called "Sphinx", completely unrelated to documentation management. Be sure you are installing the right Sphinx; it may be called ``python-sphinx``, for example. To avoid doubt, ``pip`` can be used instead as detailed below. @@ -364,13 +371,12 @@ For more detailed platform-specific instructions, see the sections below: :local: :backlinks: none - Linux ----- Most Linux distributions will include Python by default. If not, start by -installing Python (preferably Python 3). On Debian-based distros:: +installing Python 3. On Debian-based distros:: - sudo apt install python3 + sudo apt install python3 Check your package manager to see if Sphinx |sphinx_min_version| or later is available. On Debian-based distros, this package is named ``python3-sphinx``. @@ -379,11 +385,11 @@ want to use a newer Sphinx version (which may result in faster builds), you can install Sphinx through the ``pip`` package manager instead. On Debian-based distros, you can install pip with:: - sudo apt install python3-pip + sudo apt install python3-pip Once pip is available, you can then install Sphinx with:: - pip3 install sphinx + pip3 install sphinx If you run this as an unprivileged user, it may install a local copy of Sphinx for your user only. The ``sphinx-build`` executable will typically end up in @@ -398,34 +404,23 @@ macOS has Python 2.7 installed by default, but it does not have the pip package You can install Homebrew's Python 3, which includes pip, and then install the latest Sphinx using pip:: - brew install python3 - pip3 install sphinx - -Alternatively, you can simply install Sphinx directly from Homebrew:: - - brew install sphinx-doc - -This will install Sphinx for macOS's system Python 2.7, without needing pip. - -Either method works; if you plan to use Python for other purposes, it might best -to install Homebrew's Python 3 so that you have the latest Python as well as pip. -If not, just installing sphinx-doc for macOS's system Python 2.7 is fine. - + brew install python3 + pip3 install sphinx Windows ------- Python for Windows can be downloaded `from python.org `_. -The latest version of Python 3 is recommended, as it includes pip already. +The latest version of Python 3 includes pip already. You can also install Python and pip through the Chocolatey package manager. After installing Chocolatey as outlined in the `Windows compilation instructions `, run the following command from an elevated (admin) command prompt (e.g. ``cmd.exe``):: - choco install python pip -y + choco install python pip -y Once you have pip available, you can install Sphinx with the following command:: - pip install sphinx + pip install sphinx Note that this may require opening a new (admin) command prompt if you just installed pip from the same command prompt. @@ -465,8 +460,9 @@ ways to do this: By default, both HTML and text docs are built by CMake. The generated documentation is stored in ``docs/html`` and ``docs/text`` (respectively) in the -root DFHack folder, and will be installed to ``hack/docs`` when you install -DFHack. +root DFHack folder, and they will both be installed to ``hack/docs`` when you +install DFHack. The html and txt files will intermingle, but will not interfere +with one another. Running Sphinx manually ----------------------- @@ -474,8 +470,8 @@ Running Sphinx manually You can also build the documentation without running CMake - this is faster if you only want to rebuild the documentation regardless of any code changes. The ``docs/build.py`` script will build the documentation in any specified formats -(HTML only by default) using essentially the same command that CMake runs when -building the docs. Run the script with ``--help`` to see additional options. +(HTML only by default) using the same command that CMake runs when building the +docs. Run the script with ``--help`` to see additional options. Examples: @@ -501,7 +497,9 @@ or, to build plain-text output:: Sphinx has many options to enable clean builds, parallel builds, logging, and more - run ``sphinx-build --help`` for details. If you specify a different output path, be warned that Sphinx may overwrite existing files in the output -folder. +folder. Also be aware that when running ``sphinx-build`` directly, the +``docs/html`` folder may be polluted with intermediate build files that normally +get written in the cmake ``build`` directory. Building a PDF version ---------------------- @@ -546,7 +544,6 @@ closest stable release after 0.44.05-alpha1). An entry listed under a stable release like "0.44.05-r1" in changelog.txt will be listed under that release in both the stable changelog and the development changelog. - Changelog syntax ---------------- diff --git a/docs/Lua API.rst b/docs/Lua API.rst index b97c1b314..286fe97e1 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -5096,9 +5096,8 @@ the extension omitted. For example: * :file:`hack/scripts/gui/teleport.lua` is invoked as ``gui/teleport`` .. note:: - Scripts placed in subdirectories can be run as described above, but are not - listed by the `ls` command unless ``-a`` is specified. In general, scripts - should be placed in subfolders in the following situations: + In general, scripts should be placed in subfolders in the following + situations: * ``devel``: scripts that are intended exclusively for DFHack development, including examples, or scripts that are experimental and unstable @@ -5116,16 +5115,12 @@ folders can be added (for example, a copy of the :source-scripts:`scripts repository <>` for local development). See `script-paths` for more information on how to configure this behavior. -If the first line of the script is a one-line comment (starting with ``--``), -the content of the comment is used by the built-in ``ls`` and ``help`` commands. -Such a comment is required for every script in the official DFHack repository. - Scripts are read from disk when run for the first time, or if they have changed since the last time they were run. Each script has an isolated environment where global variables set by the script are stored. Values of globals persist across script runs in the same DF session. -See `devel/lua-example` for an example of this behavior. Note that local +See `devel/lua-example` for an example of this behavior. Note that ``local`` variables do *not* persist. Arguments are passed in to the scripts via the ``...`` built-in quasi-variable; @@ -5147,9 +5142,9 @@ General script API * ``dfhack.run_script(name[,args...])`` - Run a Lua script in hack/scripts/, as if it were started from the DFHack - command-line. The ``name`` argument should be the name of the script without - its extension, as it would be used on the command line. + Run a Lua script in :file:`hack/scripts/`, as if it were started from the + DFHack command-line. The ``name`` argument should be the name of the script + without its extension, as it would be used on the command line. Example: @@ -5169,10 +5164,10 @@ General script API * ``dfhack.script_help([name, [extension]])`` - Returns the contents of the embedded documentation of the specified script. - ``extension`` defaults to "lua", and ``name`` defaults to the name of the - script where this function was called. For example, the following can be used - to print the current script's help text:: + Returns the contents of the rendered (or embedded) `documentation` for the + specified script. ``extension`` defaults to "lua", and ``name`` defaults to + the name of the script where this function was called. For example, the + following can be used to print the current script's help text:: local args = {...} if args[1] == 'help' then @@ -5180,7 +5175,6 @@ General script API return end - Importing scripts ================= @@ -5234,12 +5228,12 @@ Importing scripts .. warning:: Avoid caching the table returned by ``reqscript()`` beyond storing it in - a local or global variable as in the example above. ``reqscript()`` is fast - for scripts that have previously been loaded and haven't changed. If you - retain a reference to a table returned by an old ``reqscript()`` call, this - may lead to unintended behavior if the location of the script changes - (e.g. if a save is loaded or unloaded, or if a `script path ` - is added in some other way). + a local variable as in the example above. ``reqscript()`` is fast for + scripts that have previously been loaded and haven't changed. If you retain + a reference to a table returned by an old ``reqscript()`` call, this may + lead to unintended behavior if the location of the script changes (e.g. if a + save is loaded or unloaded, or if a `script path ` is added in + some other way). .. admonition:: Tip From 4a7faeef553da99c51c230595a47ccb733769ca3 Mon Sep 17 00:00:00 2001 From: Myk Date: Mon, 12 Sep 2022 14:51:26 -0700 Subject: [PATCH 076/121] editing pass up through Custom raw tokens --- docs/guides/modding-guide.rst | 134 +++++++++++++++++----------------- 1 file changed, 69 insertions(+), 65 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index 45f922b10..2eb8c7424 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -71,100 +71,103 @@ guide. We'll explore more of the game structures `below `__ that contains XML files +describing the contents of the game's structures. These are complete, but difficult +to read (for a human). The second option is the `gui/gm-editor` script, an +interactive data explorer. You can run the script while entities like units are +selected to view the data within them. You can also run ``gui/gm-editor scr`` to +view the data for the current screen. Press :kbd:`?` while the script is active to +view help. 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 (e.g. DFHack's -Discord). +immensely, and you can always ask for help in the `right places `. Detecting triggers ------------------ -One main method for getting new behaviour into the game is callback functions. -There are two main libraries for this, ``repeat-util`` and ``eventful``. -``repeat-util`` is used to run a function once per configurable number of frames -(paused or unpaused), ticks (unpaused), in-game days, months, or years. For -adding behaviour you will most often want something to run once a tick. -``eventful`` is used to get code to run (with special parameters!) when -something happens in the game, like a reaction or job being completed or a -projectile moving. +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 +provides two libraries for this, ``repeat-util`` and `eventful `. +``repeat-util`` is used to run a function once per a configurable number of frames +(paused or unpaused), ticks (unpaused), in-game days, months, or years. If you +need to be aware the instant something happens, you'll need to run a check once a +tick. Be careful not to do this gratuitiously, though, since running that often can +slow down the game! -To get something to run once per tick, we would want to call ``repeat-util``'s -``scheduleEvery`` function. +``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 +completed or a projectile moving. -First, we load the module: :: +To get something to run once per tick, we can call ``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. -It's recommended to use something like a mod id. :: +You should use something unique, like your mod name, perhaps with a suffix if you +are registering multiple keys:: local modId = "callback-example-mod" 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() - -- Do something like iterating over all active units + -- Do something like iterating over all active units and check + -- for something interesting for _, unit in ipairs(df.global.world.units.active) do - print(unit.id) + ... end end) -``eventful`` is slightly more involved. First get the module: :: +``eventful`` is slightly more involved. First get the module:: - local eventful = require("plugins.eventful") + local eventful = require('plugins.eventful') ``eventful`` contains a table for each event which you populate with functions. Each function in the table is then called with the appropriate arguments when the event occurs. So, for example, to print the position of a moving (item) -projectile: :: +projectile:: eventful.onProjItemCheckMovement[modId] = function(projectile) print(projectile.cur_pos.x, projectile.cur_pos.y, projectile.cur_pos.z) end -Check the full list of events at -https://docs.dfhack.org/en/stable/docs/Lua%20API.html#list-of-events. +Check out the `full list of supported events ` to see what else +you can react to with ``eventful``. Custom raw tokens ----------------- @@ -172,11 +175,10 @@ Custom raw tokens In this section, we are going to use `custom raw tokens ` applied to a reaction to transfer the material of a reagent to a product as a handle improvement (like on artifact buckets), and then we are going to see how -you could make boots that make units go faster when worn. Both of these involve -custom raw tokens. +you could make boots that make units go faster when worn. First, let's define a custom crossbow with its own custom reaction. The -crossbow: :: +crossbow:: [ITEM_WEAPON:ITEM_WEAPON_CROSSBOW_SIEGE] [NAME:crossbow:crossbows] @@ -193,7 +195,7 @@ crossbow: :: [SIEGE_CROSSBOW_MOD_FIRE_RATE_MULTIPLIER:2] custom token (you'll see) The reaction to make it (you would add the reaction and not the weapon to an -entity raw): :: +entity raw):: [REACTION:MAKE_SIEGE_CROSSBOW] [NAME:make siege crossbow] @@ -214,19 +216,19 @@ So, we are going to use the ``eventful`` module to make it so that (after the script is run) when this crossbow is crafted, it will have two handles, each with the material given by the block reagents. -First, require the modules we are going to use. :: +First, require the modules we are going to use:: local eventful = require("plugins.eventful") local customRawTokens = require("custom-raw-tokens") -Now, let's make a callback: :: +Now, let's make a callback (we'll be defining the body of this function soon):: local modId = "siege-crossbow-mod" eventful.onReactionComplete[modId] = function(reaction, reactionProduct, unit, inputItems, inputReagents, outputItems) First, we check to see if it the reaction that just happened is relevant to this -callback: :: +callback:: if not customRawTokens.getToken(reaction, "SIEGE_CROSSBOW_MOD_TRANSFER_HANDLE_MATERIAL_TO_PRODUCT_IMPROVEMENT") @@ -235,28 +237,30 @@ callback: :: end Then, we get the product number listed. Next, for every reagent, if the reagent -name starts with "handle" then we get the corresponding item, and... :: +name starts with "handle" then we get the corresponding item, and... + +:: for i, reagent in ipairs(inputReagents) do - if reagent.code:sub(1, #"handle") == "handle" then + if reagent.code:startswith('handle') then -- Found handle reagent - local item = inputItems[i] -- hopefully found handle item + local item = inputItems[i] -...We then add a handle improvement to the listed product within our loop. :: +...We then add a handle improvement to the listed product within our loop:: local new = df.itemimprovement_itemspecificst:new() new.mat_type, new.mat_index = item.mat_type, item.mat_index - -- new.maker = outputItems[0].maker -- not a typical improvement new.type = df.itemimprovement_specific_type.HANDLE - outputItems[productNumber - 1].improvements:insert("#", new) + outputItems[productNumber - 1].improvements:insert('#', new) -It's all a bit loose and hacky but it works, at least if you don't have multiple -stacks filling up one reagent. +This works well as long as you don't have multiple stacks filling up one +reagent. -Let's also make some code to modify the fire rate of the siege crossbow. :: +Let's also make some code to modify the fire rate of our siege crossbow:: eventful.onProjItemCheckMovement[modId] = function(projectile) - if projectile.distance_flown > 0 then -- don't repeat this + if projectile.distance_flown > 0 then + -- don't make this adjustment more than once return end @@ -276,7 +280,7 @@ Let's also make some code to modify the fire rate of the siege crossbow. :: end Now, let's see how we could make some "pegasus boots". First, let's define the -item in the raws: :: +item in the raws:: [ITEM_SHOES:ITEM_SHOES_BOOTS_PEGASUS] [NAME:pegasus boot:pegasus boots] @@ -292,9 +296,9 @@ item in the raws: :: [LEATHER] [HARD] [PEGASUS_BOOTS_MOD_MOVEMENT_TIMER_REDUCTION_PER_TICK:5] custom raw token - (you don't have to comment this every time) + (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() @@ -306,24 +310,24 @@ their worn items: :: local amount = 0 for _, entry in ipairs(unit.inventory) do -Now, we will add up the effect of all speed-increasing gear and apply it: :: +Now, we will add up the effect of all speed-increasing gear and apply it:: if entry.mode == df.unit_inventory_item.T_mode.Worn then amount = amount + tonumber((customRawTokens.getToken(entry.item, "PEGASUS_BOOTS_MOD_MOVEMENT_TIMER_REDUCTION_PER_TICK")) or 0) end end - dfhack.units.addMoveTimer(-amount) -- Subtract amount from movement timer if - currently moving + -- Subtract amount from movement timer if currently moving + dfhack.units.addMoveTimer(-amount) The structure of a full mod --------------------------- Now, you may have noticed that you won't be able to run multiple functions on -tick/as event callbacks with that ``modId`` idea alone. To solve that we can +tick/as event callbacks with that ``modId`` key alone. To solve that we can just define all the functions we want and call them from a single function. Alternatively you can create multiple callbacks with your mod ID being a prefix, -though this way there is no guarantee about the order if that is important. You -will have to use your mod ID as a prefix if you register multiple +though this way there is no guarantee about the call order (if that is important +to you). You will have to use your mod ID as a prefix if you register multiple ``repeat-util`` callbacks, though. Create a folder for mod projects somewhere (e.g. ``hack/my-scripts/mods/``, or From cd83f3fcf13e1b6208b5266b0bc36cded20294a3 Mon Sep 17 00:00:00 2001 From: Myk Date: Mon, 12 Sep 2022 15:04:16 -0700 Subject: [PATCH 077/121] Remove bad link --- docs/guides/modding-guide.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index 2eb8c7424..549b93e3b 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -66,7 +66,7 @@ The structure of the game found in ``df.global``, containing things like the list of all items, whether to reindex pathfinding, et cetera. Also relevant to us in ``df`` are the various types found in the game, e.g. ``df.pronoun_type`` which we will be using in this -guide. We'll explore more of the game structures `below `_. +guide. We'll explore more of the game structures below. Your first script ----------------- From 04d7f0471f9a4c0d2ae696519340ca485020a974 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Tue, 13 Sep 2022 07:37:22 +0000 Subject: [PATCH 078/121] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index d0b484021..fedceb458 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit d0b484021553574c0eadfb49b30b3007856683b7 +Subproject commit fedceb458a0653f1d3f80c597888d4872b13bc46 From b89a30a3091720b64e5ea066b9f65c5cc7b6c48f Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Tue, 13 Sep 2022 10:51:38 +0100 Subject: [PATCH 079/121] Update modding-guide.rst Change use of word entities to objects because entitiy is a taken word in DF, and remove incorrect slash from a command. --- docs/guides/modding-guide.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index 549b93e3b..6687ca6d0 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -107,7 +107,7 @@ structures. The first is the ``df-structures`` `repository `__ that contains XML files describing the contents of the game's structures. These are complete, but difficult to read (for a human). The second option is the `gui/gm-editor` script, an -interactive data explorer. You can run the script while entities like units are +interactive data explorer. You can run the script while obects like units are selected to view the data within them. You can also run ``gui/gm-editor scr`` to view the data for the current screen. Press :kbd:`?` while the script is active to view help. @@ -338,7 +338,7 @@ environment for fully-functioning modular mods are as follows: * The main content of the mod would be in the ``raw`` folder: * A Lua file in ``raw/init.d/`` to initialise the mod by calling - ``your-mod-id/main/ enable``. + ``your-mod-id/main enable``. * Raw content (potentially with custom raw tokens) in ``raw/objects/``. * A subfolder for your mod in ``raw/scripts/`` containing a ``main.lua`` file (an example of which we will see) and all the modules containing the functions From 92b047fda1e1b84c7b454c7fa125db9d7ed67317 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Tue, 13 Sep 2022 10:52:56 +0100 Subject: [PATCH 080/121] Update modding-guide.rst Obects to objects typo fix --- docs/guides/modding-guide.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index 6687ca6d0..64087fd94 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -107,7 +107,7 @@ structures. The first is the ``df-structures`` `repository `__ that contains XML files describing the contents of the game's structures. These are complete, but difficult to read (for a human). The second option is the `gui/gm-editor` script, an -interactive data explorer. You can run the script while obects like units are +interactive data explorer. You can run the script while objects like units are selected to view the data within them. You can also run ``gui/gm-editor scr`` to view the data for the current screen. Press :kbd:`?` while the script is active to view help. From 7ccacd7875a2af3dfcff559c798c30413ef4ba87 Mon Sep 17 00:00:00 2001 From: Myk Date: Tue, 13 Sep 2022 23:16:54 -0700 Subject: [PATCH 081/121] editing pass for the structure of a full mod the sample code itself needs some adjustment to exemplify best pratices. --- docs/guides/modding-guide.rst | 86 +++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 40 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index 64087fd94..d6e05ea41 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -137,8 +137,7 @@ First, we load the module:: local repeatUtil = require('repeat-util') Both ``repeat-util`` and ``eventful`` require keys for registered callbacks. -You should use something unique, like your mod name, perhaps with a suffix if you -are registering multiple keys:: +You should use something unique, like your mod name:: local modId = "callback-example-mod" @@ -169,6 +168,12 @@ projectile:: Check out the `full list of supported events ` to see what else you can react to with ``eventful``. +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 +you want from a single registed callback. Alternately, you can create multiple +callbacks using different keys, using your mod ID as a key name prefix. If you do +register multiple callbacks, though, there are no guarantees about the call order. + Custom raw tokens ----------------- @@ -322,45 +327,46 @@ Now, we will add up the effect of all speed-increasing gear and apply it:: The structure of a full mod --------------------------- -Now, you may have noticed that you won't be able to run multiple functions on -tick/as event callbacks with that ``modId`` key alone. To solve that we can -just define all the functions we want and call them from a single function. -Alternatively you can create multiple callbacks with your mod ID being a prefix, -though this way there is no guarantee about the call order (if that is important -to you). You will have to use your mod ID as a prefix if you register multiple -``repeat-util`` callbacks, though. - -Create a folder for mod projects somewhere (e.g. ``hack/my-scripts/mods/``, or -maybe somewhere outside your Dwarf Fortress installation) and use your mod ID -(in hyphen-case) as the name for the mod folders within it. The structure of and -environment for fully-functioning modular mods are as follows: - -* The main content of the mod would be in the ``raw`` folder: - - * A Lua file in ``raw/init.d/`` to initialise the mod by calling - ``your-mod-id/main enable``. - * Raw content (potentially with custom raw tokens) in ``raw/objects/``. - * A subfolder for your mod in ``raw/scripts/`` containing a ``main.lua`` file - (an example of which we will see) and all the modules containing the functions - used in callbacks to ``repeat-util`` and ``eventful``. Potentially a file - containing constant definitions used by your mod (perhaps defined by the - game, like the acceleration of parabolic projectiles due to gravity - (``4900``)) too. - -* Using git within each mod folder is recommended, but not required. -* A ``readme.md`` markdown file is also recommended. -* An ``addToEntity.txt`` file containing lines to add to entity definitions for - access to mod content would be needed if applicable. -* Unless you want to merge your ``raw`` folder with your worlds every time you - make a change to your scripts, you should add - ``path/to/your-mod/raw/scripts/`` to your script paths. - -Now, let's take a look at an example ``raw/scripts/main.lua`` file. :: - - local repeatUtil = require("repeat-util") - local eventful = require("plugins.eventful") +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 +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`` +mod will be developed in the ``/path/to/mymods/example-mod`` directory and has a basic +structure that looks like this:: + + raw/init.d/example-mod.lua + raw/objects/... + raw/scripts/example-mod/main.lua + raw/scripts/example-mod/... + README.md + +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 + loaded. +* Modifications to the game raws (potentially with custom raw tokens) go in + ``raw/objects/``. +* A subfolder for your mod under ``raw/scripts/`` will contain all the scripts used by + your mod, including the main initialization code in ``main.lua`` which registers all + your timer and event callbacks. + +It is a good idea to use a version control system to organize changes to your mod code. +You can create a separate Git repository for each of your mods. The ``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 +make a change to your scripts, you should add your development scripts directory to your +script paths in ``dfhack-config/script-paths.txt``:: + + +/path/to/mymods/example-mod/raw/scripts + +Ok, you're all set up! Now, let's take a look at an example +``raw/scripts/example-mod/main.lua`` file:: + + local repeatUtil = require('repeat-util') + local eventful = require('plugins.eventful') - local modId = "example-mod" + local modId = 'example-mod' local args = {...} if args[1] == "enable" then From bb52e7bac8e560a156d21c5f7396a4fb199ac3ef Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 13 Sep 2022 22:23:04 -0700 Subject: [PATCH 082/121] list tools in categories with tags builtin --- docs/builtins/tags.rst | 20 +++++++++++++++----- library/Core.cpp | 8 +++++--- library/lua/helpdb.lua | 31 +++++++++++++++++++++++-------- test/library/helpdb.lua | 14 ++++++++++++++ 4 files changed, 57 insertions(+), 16 deletions(-) diff --git a/docs/builtins/tags.rst b/docs/builtins/tags.rst index 985943647..698323ffe 100644 --- a/docs/builtins/tags.rst +++ b/docs/builtins/tags.rst @@ -2,15 +2,25 @@ tags ==== .. dfhack-tool:: - :summary: List the strings that DFHack tools can be tagged with. + :summary: List the categories of DFHack tools or the tools with those tags. :tags: dfhack -You can find groups of related tools by passing the tag name to the `ls` -command. +DFHack tools are labeled with tags so you can find groups of related commands. +This builtin command lists the tags that you can explore, or, if called with the +name of a tag, lists the tools that have that tag. Usage ----- -:: +``tags`` + List the categories of DFHack tools and a description of those categories. +``tags `` + List the tools that are tagged with the given tag. - tags +Examples +-------- + +``tags`` + List the defined tags. +``tags design`` + List all the tools that have the ``design`` tag. diff --git a/library/Core.cpp b/library/Core.cpp index 1b3f1409f..87f78f56c 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -600,7 +600,7 @@ void help_helper(color_ostream &con, const string &entry_name) { } } -void tags_helper(color_ostream &con) { +void tags_helper(color_ostream &con, const string &tag) { CoreSuspender suspend; auto L = Lua::Core::State; Lua::StackUnwinder top(L); @@ -611,7 +611,9 @@ void tags_helper(color_ostream &con) { return; } - if (!Lua::SafeCall(con, L, 0, 0)) { + Lua::Push(L, tag); + + if (!Lua::SafeCall(con, L, 1, 0)) { con.printerr("Failed Lua call to helpdb.tags.\n"); } } @@ -712,7 +714,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v } else if (first == "tags") { - tags_helper(con); + tags_helper(con, parts.size() ? parts[0] : ""); } else if (first == "load" || first == "unload" || first == "reload") { diff --git a/library/lua/helpdb.lua b/library/lua/helpdb.lua index 805b56db9..e5359b323 100644 --- a/library/lua/helpdb.lua +++ b/library/lua/helpdb.lua @@ -719,14 +719,6 @@ local function print_columns(col1text, col2text) end end --- implements the 'tags' builtin command -function tags() - local tags = get_tags() - for _,tag in ipairs(tags) do - print_columns(tag, get_tag_data(tag).description) - end -end - -- prints the requested entries to the console. include and exclude filters are -- defined as in search_entries() above. local function list_entries(skip_tags, include, exclude) @@ -764,4 +756,27 @@ function ls(filter_str, skip_tags, show_dev_commands) show_dev_commands and {} or {tag='dev'}) end +local function list_tags() + local tags = get_tags() + for _,tag in ipairs(tags) do + print_columns(tag, get_tag_data(tag).description) + end +end + +-- implements the 'tags' builtin command +function tags(tag) + if tag and #tag > 0 and not is_tag(tag) then + dfhack.printerr(('unrecognized tag: "%s"'):format(tag)) + end + + if not is_tag(tag) then + list_tags() + return + end + + local skip_tags = true + local include = {entry_type={ENTRY_TYPES.COMMAND}, tag=tag} + list_entries(skip_tags, include) +end + return _ENV diff --git a/test/library/helpdb.lua b/test/library/helpdb.lua index 96dcbbce0..7a223bcaa 100644 --- a/test/library/helpdb.lua +++ b/test/library/helpdb.lua @@ -622,6 +622,20 @@ function test.tags() end) end +function test.tags_tag() + local mock_print = mock.func() + mock.patch(h, 'print', mock_print, function() + h.tags('armok') + expect.eq(3, mock_print.call_count) + expect.eq('bindboxers Bind your boxers.', + mock_print.call_args[1][1]) + expect.eq('boxbinders Box your binders.', + mock_print.call_args[2][1]) + expect.eq('samename Samename.', + mock_print.call_args[3][1]) + end) +end + function test.ls() local mock_print = mock.func() mock.patch(h, 'print', mock_print, function() From 759f46dbd9f92b261b7a9531f62804c07efd0756 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 14 Sep 2022 07:34:29 +0000 Subject: [PATCH 083/121] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index fedceb458..aadbe0486 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit fedceb458a0653f1d3f80c597888d4872b13bc46 +Subproject commit aadbe04860483ffbfcb4ce7e14fbb1c81bfb757a From b88d343ddd192c25107c85d3734ae3d1b84d29ce Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 14 Sep 2022 09:25:43 -0700 Subject: [PATCH 084/121] fix off-by-one error on scrollbar click detection --- library/lua/gui/widgets.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index d81fbf6b9..0069a1769 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -712,7 +712,7 @@ function Label:click_scrollbar() return 1 else local pos, height = get_scrollbar_pos_and_height(self) - if y < rect.y1 + pos then + if y <= rect.y1 + pos then return '-halfpage' elseif y > rect.y1 + pos + height then return '+halfpage' From d68c17d0708bfd79c9be223559ff53ce55e3d713 Mon Sep 17 00:00:00 2001 From: Myk Date: Wed, 14 Sep 2022 10:33:27 -0700 Subject: [PATCH 085/121] fix paths/clean up code/use best practices --- docs/guides/modding-guide.rst | 138 ++++++++++++++++------------------ 1 file changed, 65 insertions(+), 73 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index d6e05ea41..57504143a 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -336,7 +336,7 @@ structure that looks like this:: raw/init.d/example-mod.lua raw/objects/... - raw/scripts/example-mod/main.lua + raw/scripts/example-mod.lua raw/scripts/example-mod/... README.md @@ -346,9 +346,9 @@ Let's go through that line by line. loaded. * Modifications to the game raws (potentially with custom raw tokens) go in ``raw/objects/``. -* A subfolder for your mod under ``raw/scripts/`` will contain all the scripts used by - your mod, including the main initialization code in ``main.lua`` which registers all - your timer and event callbacks. +* A control script in ``raw/scripts/`` that handles enabling and disabling your mod. +* A subfolder for your mod under ``raw/scripts/`` will contain all the internal scripts + used by your mod. It is a good idea to use a version control system to organize changes to your mod code. You can create a separate Git repository for each of your mods. The ``README.md`` file @@ -361,107 +361,99 @@ script paths in ``dfhack-config/script-paths.txt``:: +/path/to/mymods/example-mod/raw/scripts Ok, you're all set up! Now, let's take a look at an example -``raw/scripts/example-mod/main.lua`` file:: - +``raw/scripts/example-mod.lua`` file:: + + -- main setup and teardown for example-mod + -- this next line indicates that the script supports the "enable" API so you can start + -- it by running "enable example-mod" and stop it by running "disable example-mod" + --@ enable = true + + local usage = [[ + Usage + ----- + + enable example-mod + disable example-mod + ]] local repeatUtil = require('repeat-util') local eventful = require('plugins.eventful') + -- you can reference global values or functions declared in any of your internal scripts + local moduleA = reqscript('example-mod/module-a') + local moduleB = reqscript('example-mod/module-b') + local moduleC = reqscript('example-mod/module-c') + local moduleD = reqscript('example-mod/module-d') + + enabled = enabled or false local modId = 'example-mod' - local args = {...} - - if args[1] == "enable" then - -- The modules and what they link into the environment with - -- Each module exports functions named the way they are to be used - local moduleA = dfhack.reqscript("example-mod/module-a") -- on load, - -- every tick - local moduleB = dfhack.reqscript("example-mod/module-b") -- on load, - -- on unload, onReactionComplete - local moduleC = dfhack.reqscript("example-mod/module-c") - -- onReactionComplete - local moduleD = dfhack.reqscript("example-mod/module-d") -- every 100 - -- frames, onProjItemCheckMovement, onProjUnitCheckMovement - - -- Set up the modules - -- Order: on load, repeat-util ticks (from smallest interval to - -- largest), days, months, years, and frames, then eventful callbacks in - -- the same order as the first modules to use them + if not dfhack_flags.enable then + print(usage) + print() + print(('Example mod is currently '):format(enabled and 'enabled' or 'disabled')) + return + end + + if dfhack_flags.enable_state then + -- do any initialization your internal scripts might require moduleA.onLoad() moduleB.onLoad() - repeatUtil.scheduleEvery(modId .. " 1 ticks", 1, "ticks", function() - moduleA.every1Tick() - end) + -- register your callbacks + repeatUtil.scheduleEvery(modId .. ' every tick', 1, 'ticks', moduleA.every1Tick) + repeatUtil.scheduleEvery(modId .. ' 100 frames', 1, 'frames', moduleD.every100Frames) - repeatUtil.scheduleEvery(modID .. " 100 frames", 1, "frames", function() - moduleD.every100Frames() + eventful.onReactionComplete[modId] = function(reaction, reaction_product, unit, input_items, input_reagents, output_items) + -- pass the event's parameters to the listeners + moduleB.onReactionComplete(reaction, reaction_product, unit, input_items, input_reagents, output_items) + moduleC.onReactionComplete(reaction, reaction_product, unit, input_items, input_reagents, output_items) end - eventful.onReactionComplete[modId] = function(...) - -- Pass the event's parameters to the listeners, whatever they are - moduleB.onReactionComplete(...) - moduleC.onReactionComplete(...) - end - - eventful.onProjItemCheckMovement[modId] = function(...) - moduleD.onProjItemCheckMovement(...) - end - - eventful.onProjUnitCheckMovement[modId] = function(...) - moduleD.onProjUnitCheckMovement(...) - end - - print("Example mod enabled") - elseif args[1] == "disable" then - -- Order: on unload, then cancel the callbacks in the same order as - -- above + eventful.onProjItemCheckMovement[modId] = moduleD.onProjItemCheckMovement + eventful.onProjUnitCheckMovement[modId] = moduleD.onProjUnitCheckMovement + print('Example mod enabled') + enabled = true + else + -- call any shutdown functions your internal scripts might require moduleA.onUnload() - repeatUtil.cancel(modId .. " 1 ticks") - repeatUtil.cancel(modId .. " 100 frames") + repeatUtil.cancel(modId .. ' every ticks') + repeatUtil.cancel(modId .. ' 100 frames') eventful.onReactionComplete[modId] = nil eventful.onProjItemCheckMovement[modId] = nil eventful.onProjUnitCheckMovement[modId] = nil - print("Example mod disabled") - elseif not args[1] then - dfhack.printerr("No argument given to example-mod/main") - else - dfhack.printerr("Unknown argument \"" .. args[1] .. - "\" to example-mod/main") + print('Example mod disabled') + enabled = false end -You can see there are four cases depending on arguments. Set up the callbacks -and call on load functions if enabled, dismantle the callbacks and call on -unload functions if disabled, no arguments given, and invalid argument(s) given. - -Here is an example of an ``raw/init.d/`` file: :: +You can call ``enable example-mod`` and ``disable example-mod`` yourself while +developing, but for end users you can start your mod automatically from +``raw/init.d/example-mod.lua``:: - dfhack.run_command("example-mod/main enable") -- Very simple. Could be - -- called "init-example-mod.lua" + dfhack.run_command('enable example-mod') -Here is what ``raw/scripts/module-a.lua`` would look like: :: +Inside ``raw/scripts/example-mod/module-a.lua`` you could have code like this:: --@ module = true - -- The above line is required for dfhack.reqscript to work + -- The above line is required for reqscript to work function onLoad() -- global variables are exported - -- blah + -- do initialization here end - local function usedByOnTick() -- local variables are not exported - -- blah + local function usedByOnTick(unit) -- local variables are not exported + -- this is an internal function: local functions/variables are not exported end function onTick() -- exported - for blah in ipairs(blah) do - usedByOnTick() + for _,unit in ipairs(df.global.world.units.all) do + usedByOnTick(unit) end end -It is recommended to check `reqscript `'s documentation. -``reqscript`` caches scripts but will reload scripts that have changed (it -checks the file's last modification date) so you can do live editing *and* have -common tables et cetera between scripts that require the same module. +The `reqscript` function reloads scripts that have changed, so you can modify your +scripts while DF is running and just disable/enable your mod to load the changes into +your ongoing game! From 51f5349bafab12d916b15a25e18bd0d363f79b04 Mon Sep 17 00:00:00 2001 From: Myk Date: Wed, 14 Sep 2022 10:43:51 -0700 Subject: [PATCH 086/121] labels must go above a section header --- docs/Lua API.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index b0891da57..e8be88626 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -5181,12 +5181,11 @@ General script API return end +.. _reqscript: Importing scripts ================= -.. _reqscript: - * ``dfhack.reqscript(name)`` or ``reqscript(name)`` Loads a Lua script and returns its environment (i.e. a table of all global From 7079fe7ea0e42381bddaa72d631d7e415bc1aa04 Mon Sep 17 00:00:00 2001 From: Myk Date: Wed, 14 Sep 2022 10:56:58 -0700 Subject: [PATCH 087/121] rewrap, fix a code syntax error --- docs/guides/modding-guide.rst | 204 +++++++++++++++++++--------------- 1 file changed, 114 insertions(+), 90 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index 57504143a..706f2cd28 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -18,21 +18,22 @@ great primer at `lua.org `__. Why not just mod the raws? -------------------------- -It depends on what you want to do. Some mods *are* better to do in just the raws. -You don't need DFHack to add a new race or modify attributes, for example. However, -DFHack scripts can do many things that you just can't do in the raws, like make a -creature that trails smoke. Some things *could* be done in the raws, but writing a -script is less hacky, easier to maintain, easier to extend, and is not prone to -side-effects. A great example is adding a syndrome when a reaction is performed. -If done in the raws, you have to create an exploding boulder to effect the syndrome. -DFHack scripts can add the syndrome directly and with much more flexibility. In the -end, complex mods will likely require a mix of raw modding and DFHack scripting. +It depends on what you want to do. Some mods *are* better to do in just the +raws. You don't need DFHack to add a new race or modify attributes, for example. +However, DFHack scripts can do many things that you just can't do in the raws, +like make a creature that trails smoke. Some things *could* be done in the raws, +but writing a script is less hacky, easier to maintain, easier to extend, and is +not prone to side-effects. A great example is adding a syndrome when a reaction +is performed. If done in the raws, you have to create an exploding boulder to +apply the syndrome. DFHack scripts can add the syndrome directly and with much +more flexibility. In the end, complex mods will likely require a mix of raw +modding and DFHack scripting. A mod-maker's development environment ------------------------------------- -While you're writing your mod, you need a place to store your in-development scripts -that will: +While you're writing your mod, you need a place to store your in-development +scripts that will: - be directly runnable by 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. 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 -Then that directory will be searched when you run DFHack commands from inside the -game. The ``+`` at the front of the path means to search that directory 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 -older copies that you may have installed in a DF directory. +Then that directory will be searched when you run DFHack commands from inside +the game. The ``+`` at the front of the path means to search that directory +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 older copies that you may have installed in a DF directory. -For scripts with the same name, the `order of precedence ` will be: +For scripts with the same name, the `order of precedence ` will +be: 1. ``own-scripts/`` 2. ``data/save/*/raw/scripts/`` @@ -88,9 +91,9 @@ If ``unit`` is ``nil``, we don't want the script to run anymore:: end 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 -map ``df.pronoun_type``. Indexing the other way, incidentally, with one of the strings, -will yield its corresponding number. So:: +a string value ("it", "she", or "he"). We get this value by indexing the +bidirectional map ``df.pronoun_type``. Indexing the other way, incidentally, +with one of the strings, will yield its corresponding number. So:: local pronounTypeString = df.pronoun_type[unit.sex] 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 structures. The first is the ``df-structures`` `repository `__ that contains XML files -describing the contents of the game's structures. These are complete, but difficult -to read (for a human). The second option is the `gui/gm-editor` script, an -interactive data explorer. You can run the script while objects like units are -selected to view the data within them. You can also run ``gui/gm-editor scr`` to -view the data for the current screen. Press :kbd:`?` while the script is active to -view help. +describing the contents of the game's structures. These are complete, but +difficult to read (for a human). The second option is the `gui/gm-editor` +script, an interactive data explorer. You can run the script while objects like +units are selected to view the data within them. You can also run +``gui/gm-editor scr`` to view the data for the current screen. Press :kbd:`?` +while the script is active to view help. 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 `. @@ -121,23 +124,23 @@ Detecting triggers 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 provides two libraries for this, ``repeat-util`` and `eventful `. -``repeat-util`` is used to run a function once per a configurable number of frames -(paused or unpaused), ticks (unpaused), in-game days, months, or years. If you -need to be aware the instant something happens, you'll need to run a check once a -tick. Be careful not to do this gratuitiously, though, since running that often can -slow down the game! +``repeat-util`` is used to run a function once per a configurable number of +frames (paused or unpaused), ticks (unpaused), in-game days, months, or years. +If you need to be aware the instant something happens, you'll need to run a +check once a tick. Be careful not to do this gratuitiously, though, since +running that often can slow down the game! ``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 -completed or a projectile moving. +only call your callback when a relevant event happens, like a reaction or job +being completed or a projectile moving. -To get something to run once per tick, we can call ``repeat-util.scheduleEvery()``. -First, we load the module:: +To get something to run once per tick, we can call +``repeat-util.scheduleEvery()``. First, we load the module:: local repeatUtil = require('repeat-util') -Both ``repeat-util`` and ``eventful`` require keys for registered callbacks. -You should use something unique, like your mod name:: +Both ``repeat-util`` and ``eventful`` require keys for registered callbacks. You +should use something unique, like your mod name:: 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:: repeatUtil.scheduleEvery(modId, 1, "ticks", function() - -- Do something like iterating over all active units and check - -- for something interesting + -- Do something like iterating over all active units and + -- check for something interesting for _, unit in ipairs(df.global.world.units.active) do ... end @@ -162,17 +165,19 @@ the event occurs. So, for example, to print the position of a moving (item) 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 Check out the `full list of supported events ` to see what else you can react to with ``eventful``. 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 -you want from a single registed callback. Alternately, you can create multiple -callbacks using different keys, using your mod ID as a key name prefix. If you do -register multiple callbacks, though, there are no guarantees about the call order. +with a single key named after your mod. You can, of course, call all the +functions you want from a single registed callback. Alternately, you can create +multiple callbacks using different keys, using your mod ID as a key name prefix. +If you do register multiple callbacks, though, there are no guarantees about the +call order. 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):: local modId = "siege-crossbow-mod" - eventful.onReactionComplete[modId] = function(reaction, reactionProduct, - unit, inputItems, inputReagents, outputItems) + eventful.onReactionComplete[modId] = function(reaction, + reactionProduct, unit, inputItems, inputReagents, + outputItems) First, we check to see if it the reaction that just happened is relevant to this callback:: @@ -279,9 +285,11 @@ Let's also make some code to modify the fire rate of our siege crossbow:: return end - local multiplier = tonumber(customRawTokens.getToken(weapon.subtype, "SIEGE_CROSSBOW_MOD_FIRE_RATE_MULTIPLIER")) or 1 - firer.counters.think_counter = math.floor(firer.counters.think_counter * - multiplier) + local multiplier = tonumber(customRawTokens.getToken( + weapon.subtype, + "SIEGE_CROSSBOW_MOD_FIRE_RATE_MULTIPLIER")) or 1 + firer.counters.think_counter = math.floor( + firer.counters.think_counter * multiplier) end 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] [HARD] [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:: repeatUtil.scheduleEvery(modId, 1, "ticks", function() -Let's iterate over every active unit, and for every unit, initialise a variable -for how much we are going to take from their movement timer and iterate over all -their worn items: :: +Let's iterate over every active unit, and for every unit, iterate over their +worn items to calculate how much we are going to take from their movement +timer:: for _, unit in ipairs(df.global.world.units.active) do local amount = 0 for _, entry in ipairs(unit.inventory) do - -Now, we will add up the effect of all speed-increasing gear and apply it:: - - if entry.mode == df.unit_inventory_item.T_mode.Worn then - amount = amount + tonumber((customRawTokens.getToken(entry.item, "PEGASUS_BOOTS_MOD_MOVEMENT_TIMER_REDUCTION_PER_TICK")) or 0) + if entry.mode == df.unit_inventory_item.T_mode.Worn then + local reduction = customRawTokens.getToken( + entry.item, + 'PEGASUS_BOOTS_MOD_MOVEMENT_TIMER_REDUCTION_PER_TICK') + amount = amount + (tonumber(reduction) or 0) + end end end + -- Subtract amount from movement timer if currently moving 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 -installation directory (e.g. ``/path/to/mymods``) and use your mod IDs as the 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`` -mod will be developed in the ``/path/to/mymods/example-mod`` directory and has a basic -structure that looks like this:: +installation directory (e.g. ``/path/to/mymods``) and use your mod IDs as the +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`` mod will be developed in the ``/path/to/mymods/example-mod`` +directory and has a basic structure that looks like this:: raw/init.d/example-mod.lua raw/objects/... @@ -342,21 +353,23 @@ structure that looks like this:: 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 - loaded. +* You'll need a short (one-line) script in ``raw/init.d/`` to initialise your + mod when a save is loaded. * Modifications to the game raws (potentially with custom raw tokens) go in ``raw/objects/``. -* A control script in ``raw/scripts/`` that handles enabling and disabling your mod. -* A subfolder for your mod under ``raw/scripts/`` will contain all the internal scripts - used by your mod. +* A control script in ``raw/scripts/`` that handles enabling and disabling 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. -You can create a separate Git repository for each of your mods. The ``README.md`` file -will be your mod help text when people browse to your online repository. +It is a good idea to use a version control system to organize changes to your +mod code. You can create a separate Git repository for each of your mods. The +``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 -make a change to your scripts, you should add your development scripts directory to your -script paths in ``dfhack-config/script-paths.txt``:: +Unless you want to install your ``raw`` folder into your DF game folder every +time you make a change to your scripts, you should add your development scripts +directory to your script paths in ``dfhack-config/script-paths.txt``:: +/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:: -- main setup and teardown for example-mod - -- this next line indicates that the script supports the "enable" API so you can start - -- it by running "enable example-mod" and stop it by running "disable example-mod" + -- this next line indicates that the script supports the "enable" + -- API so you can start it by running "enable example-mod" and stop + -- it by running "disable example-mod" --@ enable = true 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 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 moduleB = reqscript('example-mod/module-b') 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 print(usage) print() - print(('Example mod is currently '):format(enabled and 'enabled' or 'disabled')) + print(('Example mod is currently '):format( + enabled and 'enabled' or 'disabled')) return end @@ -400,13 +416,19 @@ Ok, you're all set up! Now, let's take a look at an example moduleB.onLoad() -- register your callbacks - repeatUtil.scheduleEvery(modId .. ' every tick', 1, 'ticks', moduleA.every1Tick) - repeatUtil.scheduleEvery(modId .. ' 100 frames', 1, 'frames', moduleD.every100Frames) - - eventful.onReactionComplete[modId] = function(reaction, reaction_product, unit, input_items, input_reagents, output_items) + repeatUtil.scheduleEvery(modId .. ' every tick', 1, 'ticks', + moduleA.every1Tick) + repeatUtil.scheduleEvery(modId .. ' 100 frames', 1, 'frames', + moduleD.every100Frames) + + eventful.onReactionComplete[modId] = function(reaction, + reaction_product, unit, input_items, input_reagents, + output_items) -- pass the event's parameters to the listeners - moduleB.onReactionComplete(reaction, reaction_product, unit, input_items, input_reagents, output_items) - moduleC.onReactionComplete(reaction, reaction_product, unit, input_items, input_reagents, output_items) + moduleB.onReactionComplete(reaction, reaction_product, + unit, input_items, input_reagents, output_items) + moduleC.onReactionComplete(reaction, reaction_product, + unit, input_items, input_reagents, output_items) end 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 end - local function usedByOnTick(unit) -- local variables are not exported - -- this is an internal function: local functions/variables are not exported + -- this is an internal function: local functions/variables + -- are not exported + local function usedByOnTick(unit) + -- ... end function onTick() -- exported @@ -454,6 +478,6 @@ Inside ``raw/scripts/example-mod/module-a.lua`` you could have code like this:: end end -The `reqscript` function reloads scripts that have changed, so you can modify your -scripts while DF is running and just disable/enable your mod to load the changes into -your ongoing game! +The `reqscript` function reloads scripts that have changed, so you can modify +your scripts while DF is running and just disable/enable your mod to load the +changes into your ongoing game! From b90126600f19c075284b69ef25e2794de8200d76 Mon Sep 17 00:00:00 2001 From: Myk Date: Wed, 14 Sep 2022 11:00:26 -0700 Subject: [PATCH 088/121] whiiittespaaaace!!! --- docs/guides/modding-guide.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index 706f2cd28..de07136b3 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -381,11 +381,11 @@ Ok, you're all set up! Now, let's take a look at an example -- API so you can start it by running "enable example-mod" and stop -- it by running "disable example-mod" --@ enable = true - + local usage = [[ Usage ----- - + enable example-mod disable example-mod ]] @@ -398,7 +398,7 @@ Ok, you're all set up! Now, let's take a look at an example local moduleB = reqscript('example-mod/module-b') local moduleC = reqscript('example-mod/module-c') local moduleD = reqscript('example-mod/module-d') - + enabled = enabled or false local modId = 'example-mod' From d7142749218a6c14d0b7d0aac37bc7883ae63bd1 Mon Sep 17 00:00:00 2001 From: Myk Date: Wed, 14 Sep 2022 11:24:27 -0700 Subject: [PATCH 089/121] fix reqscript link name --- docs/guides/modding-guide.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index de07136b3..3c087c80a 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -478,6 +478,6 @@ Inside ``raw/scripts/example-mod/module-a.lua`` you could have code like this:: end end -The `reqscript` function reloads scripts that have changed, so you can modify +The `reqscript ` function reloads scripts that have changed, so you can modify your scripts while DF is running and just disable/enable your mod to load the changes into your ongoing game! From ee123a24615cff76918033ad2f082a39efa95132 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Wed, 14 Sep 2022 19:41:45 +0100 Subject: [PATCH 090/121] Update modding-guide.rst Add missing slashes and change the wording to be more consistent in one part --- docs/guides/modding-guide.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index 3c087c80a..5b043c02c 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -339,10 +339,10 @@ The structure of a full mod --------------------------- 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 +installation directory (e.g. ``/path/to/mymods/``) and use your mod IDs as the 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`` mod will be developed in the ``/path/to/mymods/example-mod`` +``example-mod`` mod will be developed in the ``/path/to/mymods/example-mod/`` directory and has a basic structure that looks like this:: raw/init.d/example-mod.lua @@ -353,7 +353,7 @@ directory and has a basic structure that looks like this:: Let's go through that line by line. -* You'll need a short (one-line) script in ``raw/init.d/`` to initialise your +* A short (one-line) script in ``raw/init.d/`` to initialise your mod when a save is loaded. * Modifications to the game raws (potentially with custom raw tokens) go in ``raw/objects/``. @@ -367,11 +367,11 @@ mod code. You can create a separate Git repository for each of your mods. The ``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 +Unless you want to install your ``raw/`` folder into your DF game folder every time you make a change to your scripts, you should add your development scripts 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/ Ok, you're all set up! Now, let's take a look at an example ``raw/scripts/example-mod.lua`` file:: From 20a55299dd92681aed24cc8f3afc9da509524e58 Mon Sep 17 00:00:00 2001 From: Tachytaenius Date: Wed, 14 Sep 2022 20:04:50 +0100 Subject: [PATCH 091/121] Update modding-guide.rst Add some potentially helpful comments to explain an inconsistenecy --- docs/guides/modding-guide.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index 5b043c02c..6ec80eed1 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -421,6 +421,7 @@ Ok, you're all set up! Now, let's take a look at an example repeatUtil.scheduleEvery(modId .. ' 100 frames', 1, 'frames', moduleD.every100Frames) + -- multiple functions in the same callback eventful.onReactionComplete[modId] = function(reaction, reaction_product, unit, input_items, input_reagents, output_items) @@ -431,6 +432,8 @@ Ok, you're all set up! Now, let's take a look at an example unit, input_items, input_reagents, output_items) end + -- one function per callback (you can put them in the + -- above format if you prefer) eventful.onProjItemCheckMovement[modId] = moduleD.onProjItemCheckMovement eventful.onProjUnitCheckMovement[modId] = moduleD.onProjUnitCheckMovement From d1acd45372e4d31516a1fe4767a034c49a62f3b5 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 14 Sep 2022 13:19:10 -0700 Subject: [PATCH 092/121] add syntax highlighting to code blocks --- docs/guides/modding-guide.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index 6ec80eed1..fa0510290 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -3,6 +3,8 @@ DFHack modding guide ==================== +.. highlight:: lua + What is the difference between a script and a mod? -------------------------------------------------- @@ -182,6 +184,8 @@ call order. Custom raw tokens ----------------- +.. highlight:: none + In this section, we are going to use `custom raw tokens ` applied to a reaction to transfer the material of a reagent to a product as a handle improvement (like on artifact buckets), and then we are going to see how @@ -226,6 +230,8 @@ So, we are going to use the ``eventful`` module to make it so that (after the script is run) when this crossbow is crafted, it will have two handles, each with the material given by the block reagents. +.. highlight:: lua + First, require the modules we are going to use:: local eventful = require("plugins.eventful") @@ -292,6 +298,8 @@ Let's also make some code to modify the fire rate of our siege crossbow:: firer.counters.think_counter * multiplier) end +.. highlight:: none + Now, let's see how we could make some "pegasus boots". First, let's define the item in the raws:: @@ -312,6 +320,8 @@ item in the raws:: (you don't have to comment the custom token every time, but it does clarify what it is) +.. highlight:: lua + Then, let's make a ``repeat-util`` callback for once a tick:: repeatUtil.scheduleEvery(modId, 1, "ticks", function() From 8a36d5607b29823630279866c3c34925131e3870 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 14 Sep 2022 13:19:26 -0700 Subject: [PATCH 093/121] lcase section headers --- docs/Authors.rst | 2 +- docs/Compile.rst | 4 ++-- docs/Core.rst | 14 +++++++------- docs/Documentation.rst | 2 +- docs/Introduction.rst | 2 +- docs/Remote.rst | 4 +--- docs/api/index.rst | 2 +- docs/guides/examples-guide.rst | 2 +- docs/guides/quickfort-alias-guide.rst | 2 +- docs/guides/quickfort-library-guide.rst | 6 +++--- docs/guides/quickfort-user-guide.rst | 4 ++-- docs/index-dev.rst | 2 +- 12 files changed, 22 insertions(+), 24 deletions(-) diff --git a/docs/Authors.rst b/docs/Authors.rst index 2369d393d..280594674 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -1,4 +1,4 @@ -List of Authors +List of authors =============== The following is a list of people who have contributed to DFHack, in alphabetical order. diff --git a/docs/Compile.rst b/docs/Compile.rst index 85ec50344..0f3880816 100644 --- a/docs/Compile.rst +++ b/docs/Compile.rst @@ -40,8 +40,8 @@ This will check out the code on the default branch of the GitHub repo, currently ``develop``, which may be unstable. If you want code for the latest stable release, you can check out the ``master`` branch instead:: - git checkout master - git submodule update + git checkout master + git submodule update In general, a single DFHack clone is suitable for development - most Git operations such as switching branches can be done on an existing clone. If you diff --git a/docs/Core.rst b/docs/Core.rst index 09ae2577d..e03a7fcba 100644 --- a/docs/Core.rst +++ b/docs/Core.rst @@ -9,7 +9,7 @@ DFHack Core :depth: 2 -Command Implementation +Command implementation ====================== DFHack commands can be implemented in any of three ways: @@ -28,7 +28,7 @@ DFHack commands can be implemented in any of three ways: All tools distributed with DFHack are documented `here `. -Using DFHack Commands +Using DFHack commands ===================== DFHack commands can be executed in a number of ways: @@ -38,7 +38,7 @@ DFHack commands can be executed in a number of ways: #. From one of several `init-files`, automatically #. Using `script` to run a batch of commands from a file -The DFHack Console +The DFHack console ------------------ The command line has some nice line editing capabilities, including history that's preserved between different runs of DF - use :kbd:`↑` and :kbd:`↓` @@ -115,7 +115,7 @@ second (Windows) example uses `kill-lua` to stop a Lua script. .. _dfhack-config: -Configuration Files +Configuration files =================== Most DFHack settings can be changed by modifying files in the ``dfhack-config`` @@ -125,7 +125,7 @@ necessary. .. _init-files: -Init Files +Init files ---------- .. contents:: @@ -260,7 +260,7 @@ modified programmatically at any time through the `Lua API `. .. _env-vars: -Environment Variables +Environment variables ===================== DFHack's behavior can be adjusted with some environment variables. For example, @@ -306,7 +306,7 @@ Other (non-DFHack-specific) variables that affect DFHack: sensitive), ``DF2CONSOLE()`` will produce UTF-8-encoded text. Note that this should be the case in most UTF-8-capable \*nix terminal emulators already. -Miscellaneous Notes +Miscellaneous notes =================== This section is for odd but important notes that don't fit anywhere else. diff --git a/docs/Documentation.rst b/docs/Documentation.rst index 4109a81c7..13ae58dca 100644 --- a/docs/Documentation.rst +++ b/docs/Documentation.rst @@ -1,7 +1,7 @@ .. _documentation: ########################### -DFHack Documentation System +DFHack documentation system ########################### diff --git a/docs/Introduction.rst b/docs/Introduction.rst index ad24f8f43..d90983d8c 100644 --- a/docs/Introduction.rst +++ b/docs/Introduction.rst @@ -1,7 +1,7 @@ .. _introduction: ######################### -Introduction and Overview +Introduction and overview ######################### DFHack is a Dwarf Fortress memory access library, distributed with diff --git a/docs/Remote.rst b/docs/Remote.rst index de741f66a..6f276cdb6 100644 --- a/docs/Remote.rst +++ b/docs/Remote.rst @@ -1,7 +1,7 @@ .. _remote: ======================= -DFHack Remote Interface +DFHack remote interface ======================= DFHack provides a remote access interface that external tools can connect to and @@ -103,8 +103,6 @@ ID Method Input Output 1 RunCommand dfproto.CoreRunCommandRequest dfproto.EmptyMessage === ============ =============================== ======================= - - Conversation flow ----------------- diff --git a/docs/api/index.rst b/docs/api/index.rst index 1fe03e9a8..6afa9d6d4 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -1,5 +1,5 @@ ==================== -DFHack API Reference +DFHack API reference ==================== .. toctree:: diff --git a/docs/guides/examples-guide.rst b/docs/guides/examples-guide.rst index 74f94c20b..bcb7c77ec 100644 --- a/docs/guides/examples-guide.rst +++ b/docs/guides/examples-guide.rst @@ -1,7 +1,7 @@ .. _config-examples-guide: .. _dfhack-examples-guide: -DFHack Config File Examples +DFHack config file examples =========================== The :source:`hack/examples ` folder contains ready-to-use diff --git a/docs/guides/quickfort-alias-guide.rst b/docs/guides/quickfort-alias-guide.rst index d4be8aecd..50e5d7650 100644 --- a/docs/guides/quickfort-alias-guide.rst +++ b/docs/guides/quickfort-alias-guide.rst @@ -1,6 +1,6 @@ .. _quickfort-alias-guide: -Quickfort Keystroke Alias Reference +Quickfort keystroke alias reference =================================== Aliases allow you to use simple words to represent complicated key sequences diff --git a/docs/guides/quickfort-library-guide.rst b/docs/guides/quickfort-library-guide.rst index de5409ab3..86c82ac60 100644 --- a/docs/guides/quickfort-library-guide.rst +++ b/docs/guides/quickfort-library-guide.rst @@ -1,7 +1,7 @@ .. _blueprint-library-guide: .. _quickfort-library-guide: -Quickfort Blueprint Library +Quickfort blueprint library =========================== This guide contains a high-level overview of the blueprints available in the @@ -194,7 +194,7 @@ Extra blueprints that are useful in specific situations. - :source:`library/embark.csv ` - :source:`library/pump_stack.csv ` -Light Aquifer Tap +Light aquifer tap ~~~~~~~~~~~~~~~~~ The aquifer tap helps you create a safe, everlasting source of fresh water from @@ -216,7 +216,7 @@ blueprint that builds important starting workshops (mason, carpenter, mechanic, and craftsdwarf) and a ``#place`` blueprint that lays down a pattern of useful starting stockpiles. -Pump Stack +Pump stack ~~~~~~~~~~ The pump stack blueprints help you move water and magma up to more convenient diff --git a/docs/guides/quickfort-user-guide.rst b/docs/guides/quickfort-user-guide.rst index 9c7d3ed44..693ab033c 100644 --- a/docs/guides/quickfort-user-guide.rst +++ b/docs/guides/quickfort-user-guide.rst @@ -1,8 +1,8 @@ .. _quickfort-blueprint-guide: .. _quickfort-user-guide: -Quickfort Blueprint Editing Guide -================================= +Quickfort blueprint creation guide +================================== `Quickfort ` is a DFHack script that helps you build fortresses from "blueprint" .csv and .xlsx files. Many applications exist to edit these files, diff --git a/docs/index-dev.rst b/docs/index-dev.rst index 5a03cbedf..0e3e673d3 100644 --- a/docs/index-dev.rst +++ b/docs/index-dev.rst @@ -1,4 +1,4 @@ -======================== + ======================== DFHack Development Guide ======================== From 9117d1bef661d5d3df01e1ed45205bd8bf19adb7 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 14 Sep 2022 13:21:16 -0700 Subject: [PATCH 094/121] fix casing and spacing in dev guide header --- docs/index-dev.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/index-dev.rst b/docs/index-dev.rst index 0e3e673d3..38c458bfe 100644 --- a/docs/index-dev.rst +++ b/docs/index-dev.rst @@ -1,5 +1,5 @@ - ======================== -DFHack Development Guide +======================== +DFHack development guide ======================== These are pages relevant to people developing for DFHack. From 20919a8bb192c561ed7a6d2e98d64cc88c517034 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 14 Sep 2022 13:33:24 -0700 Subject: [PATCH 095/121] missed two upcased headers --- docs/NEWS-dev.rst | 2 +- docs/guides/index.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/NEWS-dev.rst b/docs/NEWS-dev.rst index f77737176..f77b1b4f5 100644 --- a/docs/NEWS-dev.rst +++ b/docs/NEWS-dev.rst @@ -5,7 +5,7 @@ .. _dev-changelog: ##################### -Development Changelog +Development changelog ##################### This file contains changes grouped by the release (stable or development) in diff --git a/docs/guides/index.rst b/docs/guides/index.rst index 161b1bb68..96c5688d2 100644 --- a/docs/guides/index.rst +++ b/docs/guides/index.rst @@ -1,5 +1,5 @@ =========== -User Guides +User guides =========== These pages are detailed guides covering DFHack tools. From 50772b31384f590c8607d3c116268c416a4a93d2 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Thu, 15 Sep 2022 07:31:16 +0000 Subject: [PATCH 096/121] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index aadbe0486..d7b64afc9 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit aadbe04860483ffbfcb4ce7e14fbb1c81bfb757a +Subproject commit d7b64afc9fdc80b7c1075d5f87dc4512def457a6 From c1154085cf6795d3cd7f72e4566c6356659a82d0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 15 Sep 2022 23:03:34 -0400 Subject: [PATCH 097/121] Fix tool name auto-detection for scripts in subfolders --- docs/sphinx_extensions/dfhack/tool_docs.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py index 178303744..c7883e14c 100644 --- a/docs/sphinx_extensions/dfhack/tool_docs.py +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -114,7 +114,11 @@ class DFHackToolDirectiveBase(sphinx.directives.ObjectDescription): if self.arguments: return self.arguments[0] else: - return self.env.docname.split('/')[-1] + parts = self.env.docname.split('/') + if 'tools' in parts: + return '/'.join(parts[parts.index('tools') + 1:]) + else: + return parts[-1] @staticmethod def wrap_box(*children: List[nodes.Node]) -> nodes.Admonition: From 170d7b9b519136c601528d09925912d47f9f7115 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 16 Sep 2022 15:24:18 -0700 Subject: [PATCH 098/121] typo fix in infiniteSky docs --- docs/plugins/infiniteSky.rst | 2 +- plugins/infiniteSky.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/plugins/infiniteSky.rst b/docs/plugins/infiniteSky.rst index f9a68a05a..c15f87e26 100644 --- a/docs/plugins/infiniteSky.rst +++ b/docs/plugins/infiniteSky.rst @@ -2,7 +2,7 @@ infiniteSky =========== .. dfhack-tool:: - :summary: Automatically allocates new z-levels of sky + :summary: Automatically allocate new z-levels of sky :tags: fort design map If enabled, this plugin will automatically allocate new z-levels of sky at the diff --git a/plugins/infiniteSky.cpp b/plugins/infiniteSky.cpp index bc51f06f1..b477700f4 100644 --- a/plugins/infiniteSky.cpp +++ b/plugins/infiniteSky.cpp @@ -32,7 +32,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector Date: Fri, 16 Sep 2022 15:24:33 -0700 Subject: [PATCH 099/121] fix EditField rendering with TWBT --- library/lua/gui/widgets.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 0069a1769..7076570b9 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -263,6 +263,7 @@ function EditField:onRenderBody(dc) end_pos == #txt and '' or string.char(26)) end dc:advance(self.text_offset):string(txt) + dc:string((' '):rep(dc.clip_x2 - dc.x)) end function EditField:onInput(keys) From b7357204e654bc13befd1c09c0f7fb577dd3ad08 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 16 Sep 2022 21:55:49 -0700 Subject: [PATCH 100/121] ensure artifacts dir exists for docs build --- docs/build.py | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/build.py b/docs/build.py index 6a63ed59e..bfb6780b2 100755 --- a/docs/build.py +++ b/docs/build.py @@ -16,6 +16,7 @@ class SphinxOutputFormat: def args(self): output_dir = os.path.join('docs', self.name) artifacts_dir = os.path.join('build', 'docs', self.name) # for artifacts not part of the final documentation + os.makedirs(artifacts_dir, mode=0o755, exist_ok=True) return [ *self.pre_args, '.', # source dir From ad7de34d3f1247e87ad46c2e373099c948ce7145 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 16 Sep 2022 12:15:01 -0700 Subject: [PATCH 101/121] add clickable overlay to title screen --- plugins/CMakeLists.txt | 1 + plugins/overlay.cpp | 80 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 plugins/overlay.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 49e704b75..dc341b39d 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -144,6 +144,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(mousequery mousequery.cpp) dfhack_plugin(nestboxes nestboxes.cpp) dfhack_plugin(orders orders.cpp LINK_LIBRARIES jsoncpp_static) + dfhack_plugin(overlay overlay.cpp) dfhack_plugin(pathable pathable.cpp LINK_LIBRARIES lua) dfhack_plugin(petcapRemover petcapRemover.cpp) dfhack_plugin(plants plants.cpp) diff --git a/plugins/overlay.cpp b/plugins/overlay.cpp new file mode 100644 index 000000000..42241aec6 --- /dev/null +++ b/plugins/overlay.cpp @@ -0,0 +1,80 @@ +#include "df/viewscreen_titlest.h" + +#include "modules/Gui.h" + +#include "PluginManager.h" +#include "VTableInterpose.h" +#include "uicommon.h" + +using namespace DFHack; + +DFHACK_PLUGIN("overlay"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); +REQUIRE_GLOBAL(enabler); +REQUIRE_GLOBAL(gps); + +static const std::string button_text = "[ DFHack Launcher ]"; +static bool clicked = false; + +static bool handle_click() { + int32_t x, y; + if (!enabler->tracking_on || !enabler->mouse_lbut || clicked || + !Gui::getMousePos(x, y)) + return false; + if (y == gps->dimy - 1 && x >= 1 && (size_t)x <= button_text.size()) { + clicked = true; + Core::getInstance().setHotkeyCmd("gui/launcher"); + return true; + } + return false; +} + +static void draw_widgets() { + int x = 1; + int y = gps->dimy - 1; + OutputString(COLOR_GREEN, x, y, button_text); +} + +struct title_hook : df::viewscreen_titlest { + typedef df::viewscreen_titlest interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) { + if (!handle_click()) + INTERPOSE_NEXT(feed)(input); + } + DEFINE_VMETHOD_INTERPOSE(void, render, ()) { + INTERPOSE_NEXT(render)(); + draw_widgets(); + } +}; +IMPLEMENT_VMETHOD_INTERPOSE(title_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(title_hook, render); + +DFhackCExport command_result plugin_onstatechange(color_ostream &, + state_change_event evt) { + if (evt == SC_VIEWSCREEN_CHANGED) { + clicked = false; + } + return CR_OK; +} + +DFhackCExport command_result plugin_enable(color_ostream &, bool enable) { + if (is_enabled == enable) + return CR_OK; + if (enable != is_enabled) { + if (!INTERPOSE_HOOK(title_hook, feed).apply(enable) || + !INTERPOSE_HOOK(title_hook, render).apply(enable)) + return CR_FAILURE; + + is_enabled = enable; + } + return CR_OK; +} + +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &) { + return plugin_enable(out, true); +} + +DFhackCExport command_result plugin_shutdown(color_ostream &out) { + return plugin_enable(out, false); +} From 8ab2f063df3a32b475996ef5e0e8bc10c3e70463 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 16 Sep 2022 13:38:05 -0700 Subject: [PATCH 102/121] hook remaining df screens --- plugins/overlay.cpp | 267 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 261 insertions(+), 6 deletions(-) diff --git a/plugins/overlay.cpp b/plugins/overlay.cpp index 42241aec6..9a5c625ab 100644 --- a/plugins/overlay.cpp +++ b/plugins/overlay.cpp @@ -1,4 +1,87 @@ +#include "df/viewscreen_adopt_regionst.h" +#include "df/viewscreen_adventure_logst.h" +#include "df/viewscreen_announcelistst.h" +#include "df/viewscreen_assign_display_itemst.h" +#include "df/viewscreen_barterst.h" +#include "df/viewscreen_buildinglistst.h" +#include "df/viewscreen_buildingst.h" +#include "df/viewscreen_choose_start_sitest.h" +#include "df/viewscreen_civlistst.h" +#include "df/viewscreen_counterintelligencest.h" +#include "df/viewscreen_createquotast.h" +#include "df/viewscreen_customize_unitst.h" +#include "df/viewscreen_dungeonmodest.h" +#include "df/viewscreen_dungeon_monsterstatusst.h" +#include "df/viewscreen_dungeon_wrestlest.h" +#include "df/viewscreen_dwarfmodest.h" +#include "df/viewscreen_entityst.h" +#include "df/viewscreen_export_graphical_mapst.h" +#include "df/viewscreen_export_regionst.h" +#include "df/viewscreen_game_cleanerst.h" +#include "df/viewscreen_image_creator_mode.h" +#include "df/viewscreen_image_creatorst.h" +#include "df/viewscreen_itemst.h" +#include "df/viewscreen_joblistst.h" +#include "df/viewscreen_jobmanagementst.h" +#include "df/viewscreen_jobst.h" +#include "df/viewscreen_justicest.h" +#include "df/viewscreen_kitchenpref_page.h" +#include "df/viewscreen_kitchenprefst.h" +#include "df/viewscreen_layer_arena_creaturest.h" +#include "df/viewscreen_layer_assigntradest.h" +#include "df/viewscreen_layer_choose_language_namest.h" +#include "df/viewscreen_layer_currencyst.h" +#include "df/viewscreen_layer_export_play_mapst.h" +#include "df/viewscreen_layer.h" +#include "df/viewscreen_layer_militaryst.h" +#include "df/viewscreen_layer_musicsoundst.h" +#include "df/viewscreen_layer_noblelistst.h" +#include "df/viewscreen_layer_overall_healthst.h" +#include "df/viewscreen_layer_reactionst.h" +#include "df/viewscreen_layer_squad_schedulest.h" +#include "df/viewscreen_layer_stockpilest.h" +#include "df/viewscreen_layer_stone_restrictionst.h" +#include "df/viewscreen_layer_unit_actionst.h" +#include "df/viewscreen_layer_unit_healthst.h" +#include "df/viewscreen_layer_unit_relationshipst.h" +#include "df/viewscreen_layer_world_gen_param_presetst.h" +#include "df/viewscreen_layer_world_gen_paramst.h" +#include "df/viewscreen_legendsst.h" +#include "df/viewscreen_loadgamest.h" +#include "df/viewscreen_locationsst.h" +#include "df/viewscreen_meetingst.h" +#include "df/viewscreen_movieplayerst.h" +#include "df/viewscreen_new_regionst.h" +#include "df/viewscreen_noblest.h" +#include "df/viewscreen_optionst.h" +#include "df/viewscreen_overallstatusst.h" +#include "df/viewscreen_petitionsst.h" +#include "df/viewscreen_petst.h" +#include "df/viewscreen_pricest.h" +#include "df/viewscreen_reportlistst.h" +#include "df/viewscreen_requestagreementst.h" +#include "df/viewscreen_savegamest.h" +#include "df/viewscreen_selectitemst.h" +#include "df/viewscreen_setupadventurest.h" +#include "df/viewscreen_setupdwarfgamest.h" +#include "df/viewscreen_storesst.h" +#include "df/viewscreen_textviewerst.h" #include "df/viewscreen_titlest.h" +#include "df/viewscreen_topicmeeting_fill_land_holder_positionsst.h" +#include "df/viewscreen_topicmeetingst.h" +#include "df/viewscreen_topicmeeting_takerequestsst.h" +#include "df/viewscreen_tradeagreementst.h" +#include "df/viewscreen_tradegoodsst.h" +#include "df/viewscreen_tradelistst.h" +#include "df/viewscreen_treasurelistst.h" +#include "df/viewscreen_unitlist_page.h" +#include "df/viewscreen_unitlistst.h" +#include "df/viewscreen_unitst.h" +#include "df/viewscreen_update_regionst.h" +#include "df/viewscreen_wagesst.h" +#include "df/viewscreen_workquota_conditionst.h" +#include "df/viewscreen_workquota_detailsst.h" +#include "df/viewscreen_workshop_profilest.h" #include "modules/Gui.h" @@ -35,8 +118,9 @@ static void draw_widgets() { OutputString(COLOR_GREEN, x, y, button_text); } -struct title_hook : df::viewscreen_titlest { - typedef df::viewscreen_titlest interpose_base; +template +struct viewscreen_overlay : T { + typedef T interpose_base; DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) { if (!handle_click()) @@ -47,8 +131,94 @@ struct title_hook : df::viewscreen_titlest { draw_widgets(); } }; -IMPLEMENT_VMETHOD_INTERPOSE(title_hook, feed); -IMPLEMENT_VMETHOD_INTERPOSE(title_hook, render); + +#define IMPLEMENT_HOOKS(screen) \ + typedef viewscreen_overlay screen##_overlay; \ + template<> IMPLEMENT_VMETHOD_INTERPOSE(screen##_overlay, feed); \ + template<> IMPLEMENT_VMETHOD_INTERPOSE(screen##_overlay, render); + +IMPLEMENT_HOOKS(adopt_region) +IMPLEMENT_HOOKS(adventure_log) +IMPLEMENT_HOOKS(announcelist) +IMPLEMENT_HOOKS(assign_display_item) +IMPLEMENT_HOOKS(barter) +IMPLEMENT_HOOKS(buildinglist) +IMPLEMENT_HOOKS(building) +IMPLEMENT_HOOKS(choose_start_site) +IMPLEMENT_HOOKS(civlist) +IMPLEMENT_HOOKS(counterintelligence) +IMPLEMENT_HOOKS(createquota) +IMPLEMENT_HOOKS(customize_unit) +IMPLEMENT_HOOKS(dungeonmode) +IMPLEMENT_HOOKS(dungeon_monsterstatus) +IMPLEMENT_HOOKS(dungeon_wrestle) +IMPLEMENT_HOOKS(dwarfmode) +IMPLEMENT_HOOKS(entity) +IMPLEMENT_HOOKS(export_graphical_map) +IMPLEMENT_HOOKS(export_region) +IMPLEMENT_HOOKS(game_cleaner) +IMPLEMENT_HOOKS(image_creator) +IMPLEMENT_HOOKS(item) +IMPLEMENT_HOOKS(joblist) +IMPLEMENT_HOOKS(jobmanagement) +IMPLEMENT_HOOKS(job) +IMPLEMENT_HOOKS(justice) +IMPLEMENT_HOOKS(kitchenpref) +IMPLEMENT_HOOKS(layer_arena_creature) +IMPLEMENT_HOOKS(layer_assigntrade) +IMPLEMENT_HOOKS(layer_choose_language_name) +IMPLEMENT_HOOKS(layer_currency) +IMPLEMENT_HOOKS(layer_export_play_map) +IMPLEMENT_HOOKS(layer_military) +IMPLEMENT_HOOKS(layer_musicsound) +IMPLEMENT_HOOKS(layer_noblelist) +IMPLEMENT_HOOKS(layer_overall_health) +IMPLEMENT_HOOKS(layer_reaction) +IMPLEMENT_HOOKS(layer_squad_schedule) +IMPLEMENT_HOOKS(layer_stockpile) +IMPLEMENT_HOOKS(layer_stone_restriction) +IMPLEMENT_HOOKS(layer_unit_action) +IMPLEMENT_HOOKS(layer_unit_health) +IMPLEMENT_HOOKS(layer_unit_relationship) +IMPLEMENT_HOOKS(layer_world_gen_param_preset) +IMPLEMENT_HOOKS(layer_world_gen_param) +IMPLEMENT_HOOKS(legends) +IMPLEMENT_HOOKS(loadgame) +IMPLEMENT_HOOKS(locations) +IMPLEMENT_HOOKS(meeting) +IMPLEMENT_HOOKS(movieplayer) +IMPLEMENT_HOOKS(new_region) +IMPLEMENT_HOOKS(noble) +IMPLEMENT_HOOKS(option) +IMPLEMENT_HOOKS(overallstatus) +IMPLEMENT_HOOKS(petitions) +IMPLEMENT_HOOKS(pet) +IMPLEMENT_HOOKS(price) +IMPLEMENT_HOOKS(reportlist) +IMPLEMENT_HOOKS(requestagreement) +IMPLEMENT_HOOKS(savegame) +IMPLEMENT_HOOKS(selectitem) +IMPLEMENT_HOOKS(setupadventure) +IMPLEMENT_HOOKS(setupdwarfgame) +IMPLEMENT_HOOKS(stores) +IMPLEMENT_HOOKS(textviewer) +IMPLEMENT_HOOKS(title) +IMPLEMENT_HOOKS(topicmeeting_fill_land_holder_positions) +IMPLEMENT_HOOKS(topicmeeting) +IMPLEMENT_HOOKS(topicmeeting_takerequests) +IMPLEMENT_HOOKS(tradeagreement) +IMPLEMENT_HOOKS(tradegoods) +IMPLEMENT_HOOKS(tradelist) +IMPLEMENT_HOOKS(treasurelist) +IMPLEMENT_HOOKS(unitlist) +IMPLEMENT_HOOKS(unit) +IMPLEMENT_HOOKS(update_region) +IMPLEMENT_HOOKS(wages) +IMPLEMENT_HOOKS(workquota_condition) +IMPLEMENT_HOOKS(workquota_details) +IMPLEMENT_HOOKS(workshop_profile) + +#undef IMPLEMENT_HOOKS DFhackCExport command_result plugin_onstatechange(color_ostream &, state_change_event evt) { @@ -58,12 +228,95 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &, return CR_OK; } +#define INTERPOSE_HOOKS_FAILED(screen) \ + !INTERPOSE_HOOK(screen##_overlay, feed).apply(enable) || \ + !INTERPOSE_HOOK(screen##_overlay, render).apply(enable) + DFhackCExport command_result plugin_enable(color_ostream &, bool enable) { if (is_enabled == enable) return CR_OK; + if (enable != is_enabled) { - if (!INTERPOSE_HOOK(title_hook, feed).apply(enable) || - !INTERPOSE_HOOK(title_hook, render).apply(enable)) + if (INTERPOSE_HOOKS_FAILED(adopt_region) || + INTERPOSE_HOOKS_FAILED(adventure_log) || + INTERPOSE_HOOKS_FAILED(announcelist) || + INTERPOSE_HOOKS_FAILED(assign_display_item) || + INTERPOSE_HOOKS_FAILED(barter) || + INTERPOSE_HOOKS_FAILED(buildinglist) || + INTERPOSE_HOOKS_FAILED(building) || + INTERPOSE_HOOKS_FAILED(choose_start_site) || + INTERPOSE_HOOKS_FAILED(civlist) || + INTERPOSE_HOOKS_FAILED(counterintelligence) || + INTERPOSE_HOOKS_FAILED(createquota) || + INTERPOSE_HOOKS_FAILED(customize_unit) || + INTERPOSE_HOOKS_FAILED(dungeonmode) || + INTERPOSE_HOOKS_FAILED(dungeon_monsterstatus) || + INTERPOSE_HOOKS_FAILED(dungeon_wrestle) || + INTERPOSE_HOOKS_FAILED(dwarfmode) || + INTERPOSE_HOOKS_FAILED(entity) || + INTERPOSE_HOOKS_FAILED(export_graphical_map) || + INTERPOSE_HOOKS_FAILED(export_region) || + INTERPOSE_HOOKS_FAILED(game_cleaner) || + INTERPOSE_HOOKS_FAILED(image_creator) || + INTERPOSE_HOOKS_FAILED(item) || + INTERPOSE_HOOKS_FAILED(joblist) || + INTERPOSE_HOOKS_FAILED(jobmanagement) || + INTERPOSE_HOOKS_FAILED(job) || + INTERPOSE_HOOKS_FAILED(justice) || + INTERPOSE_HOOKS_FAILED(kitchenpref) || + INTERPOSE_HOOKS_FAILED(layer_arena_creature) || + INTERPOSE_HOOKS_FAILED(layer_assigntrade) || + INTERPOSE_HOOKS_FAILED(layer_choose_language_name) || + INTERPOSE_HOOKS_FAILED(layer_currency) || + INTERPOSE_HOOKS_FAILED(layer_export_play_map) || + INTERPOSE_HOOKS_FAILED(layer_military) || + INTERPOSE_HOOKS_FAILED(layer_musicsound) || + INTERPOSE_HOOKS_FAILED(layer_noblelist) || + INTERPOSE_HOOKS_FAILED(layer_overall_health) || + INTERPOSE_HOOKS_FAILED(layer_reaction) || + INTERPOSE_HOOKS_FAILED(layer_squad_schedule) || + INTERPOSE_HOOKS_FAILED(layer_stockpile) || + INTERPOSE_HOOKS_FAILED(layer_stone_restriction) || + INTERPOSE_HOOKS_FAILED(layer_unit_action) || + INTERPOSE_HOOKS_FAILED(layer_unit_health) || + INTERPOSE_HOOKS_FAILED(layer_unit_relationship) || + INTERPOSE_HOOKS_FAILED(layer_world_gen_param_preset) || + INTERPOSE_HOOKS_FAILED(layer_world_gen_param) || + INTERPOSE_HOOKS_FAILED(legends) || + INTERPOSE_HOOKS_FAILED(loadgame) || + INTERPOSE_HOOKS_FAILED(locations) || + INTERPOSE_HOOKS_FAILED(meeting) || + INTERPOSE_HOOKS_FAILED(movieplayer) || + INTERPOSE_HOOKS_FAILED(new_region) || + INTERPOSE_HOOKS_FAILED(noble) || + INTERPOSE_HOOKS_FAILED(option) || + INTERPOSE_HOOKS_FAILED(overallstatus) || + INTERPOSE_HOOKS_FAILED(petitions) || + INTERPOSE_HOOKS_FAILED(pet) || + INTERPOSE_HOOKS_FAILED(price) || + INTERPOSE_HOOKS_FAILED(reportlist) || + INTERPOSE_HOOKS_FAILED(requestagreement) || + INTERPOSE_HOOKS_FAILED(savegame) || + INTERPOSE_HOOKS_FAILED(selectitem) || + INTERPOSE_HOOKS_FAILED(setupadventure) || + INTERPOSE_HOOKS_FAILED(setupdwarfgame) || + INTERPOSE_HOOKS_FAILED(stores) || + INTERPOSE_HOOKS_FAILED(textviewer) || + INTERPOSE_HOOKS_FAILED(title) || + INTERPOSE_HOOKS_FAILED(topicmeeting_fill_land_holder_positions) || + INTERPOSE_HOOKS_FAILED(topicmeeting) || + INTERPOSE_HOOKS_FAILED(topicmeeting_takerequests) || + INTERPOSE_HOOKS_FAILED(tradeagreement) || + INTERPOSE_HOOKS_FAILED(tradegoods) || + INTERPOSE_HOOKS_FAILED(tradelist) || + INTERPOSE_HOOKS_FAILED(treasurelist) || + INTERPOSE_HOOKS_FAILED(unitlist) || + INTERPOSE_HOOKS_FAILED(unit) || + INTERPOSE_HOOKS_FAILED(update_region) || + INTERPOSE_HOOKS_FAILED(wages) || + INTERPOSE_HOOKS_FAILED(workquota_condition) || + INTERPOSE_HOOKS_FAILED(workquota_details) || + INTERPOSE_HOOKS_FAILED(workshop_profile)) return CR_FAILURE; is_enabled = enable; @@ -71,6 +324,8 @@ DFhackCExport command_result plugin_enable(color_ostream &, bool enable) { return CR_OK; } +#undef INTERPOSE_HOOKS_FAILED + DFhackCExport command_result plugin_init(color_ostream &out, std::vector &) { return plugin_enable(out, true); } From 089d2b6c33d89e3c05a74c5e5f8a5539a8c5356a Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 16 Sep 2022 13:43:37 -0700 Subject: [PATCH 103/121] move dwarfmonitor's weather indicator to the right --- dfhack-config/dwarfmonitor.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dfhack-config/dwarfmonitor.json b/dfhack-config/dwarfmonitor.json index 3fd365e74..007dad020 100644 --- a/dfhack-config/dwarfmonitor.json +++ b/dfhack-config/dwarfmonitor.json @@ -2,7 +2,7 @@ "widgets": [ { "type": "weather", - "x": 1, + "x": 22, "y": -1 }, { From 4800037ad36d3059c584ff523e4c9275616d416b Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 16 Sep 2022 13:49:46 -0700 Subject: [PATCH 104/121] in dwarf mode, we must use lbut_down --- plugins/overlay.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/overlay.cpp b/plugins/overlay.cpp index 9a5c625ab..c4bbfb95b 100644 --- a/plugins/overlay.cpp +++ b/plugins/overlay.cpp @@ -101,7 +101,7 @@ static bool clicked = false; static bool handle_click() { int32_t x, y; - if (!enabler->tracking_on || !enabler->mouse_lbut || clicked || + if (!enabler->tracking_on || !enabler->mouse_lbut_down || clicked || !Gui::getMousePos(x, y)) return false; if (y == gps->dimy - 1 && x >= 1 && (size_t)x <= button_text.size()) { From 301563d1e643fa6249c3d7c1708c221482df66d5 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 16 Sep 2022 13:52:06 -0700 Subject: [PATCH 105/121] update changelog --- docs/changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index f46b197ab..c3f8996c3 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -36,6 +36,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## New Plugins - `autonestbox`: split off from `zone` into its own plugin. Note that to enable, the command has changed from ``autonestbox start`` to ``enable autonestbox``. - `autobutcher`: split off from `zone` into its own plugin. Note that to enable, the command has changed from ``autobutcher start`` to ``enable autobutcher``. +- `overlay`: display a "DFHack" button in the lower left corner that you can click to start the new GUI command launcher. ## New Tweaks From b084171048657a4080bec4d0d67ab64ce88dfa11 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 16 Sep 2022 14:01:29 -0700 Subject: [PATCH 106/121] add overlay docs --- docs/plugins/overlay.rst | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 docs/plugins/overlay.rst diff --git a/docs/plugins/overlay.rst b/docs/plugins/overlay.rst new file mode 100644 index 000000000..9416fba31 --- /dev/null +++ b/docs/plugins/overlay.rst @@ -0,0 +1,20 @@ +overlay +======= + +.. dfhack-tool:: + :summary: Provide an on-screen clickable DFHack launcher button. + :tags: dfhack interface + +This tool places a small button in the lower left corner of the screen that you +can click to run DFHack commands with `gui/launcher`. + +If you would rather always run `gui/launcher` with the hotkeys, or just don't +want the DFHack button on-screen, just disable the plugin with +``disable overlay``. + +Usage +----- + +:: + + enable overlay From 3feca2ae58e6977326130f0bad82d9cc6f9252ee Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 16 Sep 2022 16:19:21 -0700 Subject: [PATCH 107/121] make the overlay work with TWBT --- plugins/overlay.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/plugins/overlay.cpp b/plugins/overlay.cpp index c4bbfb95b..386b4b532 100644 --- a/plugins/overlay.cpp +++ b/plugins/overlay.cpp @@ -1,3 +1,5 @@ +#include "df/renderer.h" + #include "df/viewscreen_adopt_regionst.h" #include "df/viewscreen_adventure_logst.h" #include "df/viewscreen_announcelistst.h" @@ -83,8 +85,7 @@ #include "df/viewscreen_workquota_detailsst.h" #include "df/viewscreen_workshop_profilest.h" -#include "modules/Gui.h" - +#include "Debug.h" #include "PluginManager.h" #include "VTableInterpose.h" #include "uicommon.h" @@ -96,14 +97,23 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled); REQUIRE_GLOBAL(enabler); REQUIRE_GLOBAL(gps); +namespace DFHack { + DBG_DECLARE(overlay, log, DebugCategory::LINFO); +} + static const std::string button_text = "[ DFHack Launcher ]"; static bool clicked = false; static bool handle_click() { - int32_t x, y; - if (!enabler->tracking_on || !enabler->mouse_lbut_down || clicked || - !Gui::getMousePos(x, y)) + int32_t x = 0, y = 0; + if (!enabler->mouse_lbut_down || clicked || + !enabler->renderer->get_mouse_coords(&x, &y)) { + DEBUG(log).print( + "lbut_down=%s; clicked=%s; mouse_x=%d; mouse_y=%d\n", + enabler->mouse_lbut_down ? "true" : "false", + clicked ? "true" : "false", x, y); return false; + } if (y == gps->dimy - 1 && x >= 1 && (size_t)x <= button_text.size()) { clicked = true; Core::getInstance().setHotkeyCmd("gui/launcher"); From d0a5db2a0d4b4aed4e6d4dcee24bb92393729a22 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 16 Sep 2022 20:59:02 -0700 Subject: [PATCH 108/121] set sentinel value used by TWBT --- library/include/modules/Renderer.h | 7 +++++++ library/modules/Renderer.cpp | 2 ++ plugins/overlay.cpp | 6 +++--- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/library/include/modules/Renderer.h b/library/include/modules/Renderer.h index 11abd7c1d..65a9694ab 100644 --- a/library/include/modules/Renderer.h +++ b/library/include/modules/Renderer.h @@ -5,6 +5,13 @@ #pragma once namespace DFHack { namespace Renderer { + // If the the 'x' parameter points to this value, then the 'y' parameter will + // be interpreted as a boolean flag for whether to return map coordinates (false) + // or text tile coordinates (true). Only TWBT implements this logic, and this + // sentinel value can be removed once DF provides an API for retrieving the + // two sets of coordinates. + DFHACK_EXPORT extern const int32_t GET_MOUSE_COORDS_SENTINEL; + struct DFHACK_EXPORT renderer_wrap : public df::renderer { void set_to_null(); void copy_from_parent(); diff --git a/library/modules/Renderer.cpp b/library/modules/Renderer.cpp index 474dc3656..b746a149c 100644 --- a/library/modules/Renderer.cpp +++ b/library/modules/Renderer.cpp @@ -9,6 +9,8 @@ using DFHack::Renderer::renderer_wrap; static renderer_wrap *original_renderer = NULL; +const int32_t Renderer::GET_MOUSE_COORDS_SENTINEL = 0xcd1aa471; + bool init() { if (!original_renderer) diff --git a/plugins/overlay.cpp b/plugins/overlay.cpp index 386b4b532..b8c4e1fb4 100644 --- a/plugins/overlay.cpp +++ b/plugins/overlay.cpp @@ -1,5 +1,3 @@ -#include "df/renderer.h" - #include "df/viewscreen_adopt_regionst.h" #include "df/viewscreen_adventure_logst.h" #include "df/viewscreen_announcelistst.h" @@ -90,6 +88,8 @@ #include "VTableInterpose.h" #include "uicommon.h" +#include "modules/Renderer.h" + using namespace DFHack; DFHACK_PLUGIN("overlay"); @@ -105,7 +105,7 @@ static const std::string button_text = "[ DFHack Launcher ]"; static bool clicked = false; static bool handle_click() { - int32_t x = 0, y = 0; + int32_t x = Renderer::GET_MOUSE_COORDS_SENTINEL, y = (int32_t)true; if (!enabler->mouse_lbut_down || clicked || !enabler->renderer->get_mouse_coords(&x, &y)) { DEBUG(log).print( From dc5010c2e27ed1767c32893b8eccd86e6ebe3c47 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 16 Sep 2022 22:05:08 -0700 Subject: [PATCH 109/121] don't block text on the new region screen --- plugins/overlay.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/plugins/overlay.cpp b/plugins/overlay.cpp index b8c4e1fb4..1e66814a0 100644 --- a/plugins/overlay.cpp +++ b/plugins/overlay.cpp @@ -51,7 +51,6 @@ #include "df/viewscreen_locationsst.h" #include "df/viewscreen_meetingst.h" #include "df/viewscreen_movieplayerst.h" -#include "df/viewscreen_new_regionst.h" #include "df/viewscreen_noblest.h" #include "df/viewscreen_optionst.h" #include "df/viewscreen_overallstatusst.h" @@ -197,7 +196,6 @@ IMPLEMENT_HOOKS(loadgame) IMPLEMENT_HOOKS(locations) IMPLEMENT_HOOKS(meeting) IMPLEMENT_HOOKS(movieplayer) -IMPLEMENT_HOOKS(new_region) IMPLEMENT_HOOKS(noble) IMPLEMENT_HOOKS(option) IMPLEMENT_HOOKS(overallstatus) @@ -297,7 +295,6 @@ DFhackCExport command_result plugin_enable(color_ostream &, bool enable) { INTERPOSE_HOOKS_FAILED(locations) || INTERPOSE_HOOKS_FAILED(meeting) || INTERPOSE_HOOKS_FAILED(movieplayer) || - INTERPOSE_HOOKS_FAILED(new_region) || INTERPOSE_HOOKS_FAILED(noble) || INTERPOSE_HOOKS_FAILED(option) || INTERPOSE_HOOKS_FAILED(overallstatus) || From 888288212bd998c281532cac5141ed35244f9c39 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Sat, 17 Sep 2022 07:19:03 +0000 Subject: [PATCH 110/121] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index d7b64afc9..a422b1bf7 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit d7b64afc9fdc80b7c1075d5f87dc4512def457a6 +Subproject commit a422b1bf7ff99d578b59540108fe74eca38606d2 From 448eced17cf8963434ebfe2711d7a046c22354f2 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 18 Sep 2022 08:07:08 -0700 Subject: [PATCH 111/121] make Screen::getMousePos always return scr coords --- library/modules/Screen.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index 5001a34c0..bff14a380 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -31,6 +31,7 @@ distribution. #include using namespace std; +#include "modules/Renderer.h" #include "modules/Screen.h" #include "modules/GuiHooks.h" #include "MemAccess.h" @@ -75,12 +76,14 @@ using std::string; * Screen painting API. */ +// returns text grid coordinates, even if the game map is scaled differently df::coord2d Screen::getMousePos() { - if (!gps || (enabler && !enabler->tracking_on)) + int32_t x = Renderer::GET_MOUSE_COORDS_SENTINEL, y = (int32_t)true; + if (!enabler || !enabler->renderer->get_mouse_coords(&x, &y)) { return df::coord2d(-1, -1); - - return df::coord2d(gps->mouse_x, gps->mouse_y); + } + return df::coord2d(x, y); } df::coord2d Screen::getWindowSize() @@ -769,7 +772,7 @@ int dfhack_lua_viewscreen::do_input(lua_State *L) } } - if (enabler && enabler->tracking_on) + if (enabler) { if (enabler->mouse_lbut_down) { lua_pushboolean(L, true); From 3b89f482d11e448cb70f5f4247413d990aef47d6 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 18 Sep 2022 08:18:08 -0700 Subject: [PATCH 112/121] use the new Screen::getMousePos() implementation --- plugins/overlay.cpp | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/plugins/overlay.cpp b/plugins/overlay.cpp index 1e66814a0..30a5546a4 100644 --- a/plugins/overlay.cpp +++ b/plugins/overlay.cpp @@ -87,7 +87,7 @@ #include "VTableInterpose.h" #include "uicommon.h" -#include "modules/Renderer.h" +#include "modules/Screen.h" using namespace DFHack; @@ -104,16 +104,12 @@ static const std::string button_text = "[ DFHack Launcher ]"; static bool clicked = false; static bool handle_click() { - int32_t x = Renderer::GET_MOUSE_COORDS_SENTINEL, y = (int32_t)true; - if (!enabler->mouse_lbut_down || clicked || - !enabler->renderer->get_mouse_coords(&x, &y)) { - DEBUG(log).print( - "lbut_down=%s; clicked=%s; mouse_x=%d; mouse_y=%d\n", - enabler->mouse_lbut_down ? "true" : "false", - clicked ? "true" : "false", x, y); + if (!enabler->mouse_lbut_down || clicked) { return false; } - if (y == gps->dimy - 1 && x >= 1 && (size_t)x <= button_text.size()) { + df::coord2d pos = Screen::getMousePos(); + DEBUG(log).print("clicked at screen coordinates (%d, %d)\n", pos.x, pos.y); + if (pos.y == gps->dimy - 1 && pos.x >= 1 && (size_t)pos.x <= button_text.size()) { clicked = true; Core::getInstance().setHotkeyCmd("gui/launcher"); return true; From 215a5b0a24be00d91dcf9376680e6311cf3df574 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 18 Sep 2022 08:19:02 -0700 Subject: [PATCH 113/121] add Gui::getMousePos() that always gets map coords --- library/include/modules/Gui.h | 5 ++--- library/modules/Gui.cpp | 17 ++++++++--------- plugins/devel/kittens.cpp | 11 +++++------ plugins/mousequery.cpp | 20 ++++---------------- 4 files changed, 19 insertions(+), 34 deletions(-) diff --git a/library/include/modules/Gui.h b/library/include/modules/Gui.h index 455032fea..a0ae27889 100644 --- a/library/include/modules/Gui.h +++ b/library/include/modules/Gui.h @@ -126,10 +126,11 @@ namespace DFHack DFHACK_EXPORT void showAutoAnnouncement(df::announcement_type type, df::coord pos, std::string message, int color = 7, bool bright = true, df::unit *unit1 = NULL, df::unit *unit2 = NULL); /* - * Cursor and window coords + * Cursor and window map coords */ DFHACK_EXPORT df::coord getViewportPos(); DFHACK_EXPORT df::coord getCursorPos(); + DFHACK_EXPORT df::coord getMousePos(); static const int AREA_MAP_WIDTH = 23; static const int MENU_WIDTH = 30; @@ -162,8 +163,6 @@ namespace DFHack DFHACK_EXPORT bool getDesignationCoords (int32_t &x, int32_t &y, int32_t &z); DFHACK_EXPORT bool setDesignationCoords (const int32_t x, const int32_t y, const int32_t z); - DFHACK_EXPORT bool getMousePos (int32_t & x, int32_t & y); - // The distance from the z-level of the tile at map coordinates (x, y) to the closest ground z-level below // Defaults to 0, unless overriden by plugins DFHACK_EXPORT int getDepthAt (int32_t x, int32_t y); diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index fc78bb57d..f9e15aac4 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -1831,17 +1831,16 @@ bool Gui::setDesignationCoords (const int32_t x, const int32_t y, const int32_t return true; } -bool Gui::getMousePos (int32_t & x, int32_t & y) +// returns the map coordinates that the mouse cursor is over +df::coord Gui::getMousePos() { - if (gps) { - x = gps->mouse_x; - y = gps->mouse_y; - } - else { - x = -1; - y = -1; + df::coord pos; + if (gps && gps->mouse_x > -1) { + pos = getViewportPos(); + pos.x += gps->mouse_x - 1; + pos.y += gps->mouse_y - 1; } - return (x == -1) ? false : true; + return pos; } int getDepthAt_default (int32_t x, int32_t y) diff --git a/plugins/devel/kittens.cpp b/plugins/devel/kittens.cpp index fce924aff..56ca813ae 100644 --- a/plugins/devel/kittens.cpp +++ b/plugins/devel/kittens.cpp @@ -135,13 +135,12 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) last_designation[2] = desig_z; out.print("Designation: %d %d %d\n",desig_x, desig_y, desig_z); } - int mouse_x, mouse_y; - Gui::getMousePos(mouse_x,mouse_y); - if(mouse_x != last_mouse[0] || mouse_y != last_mouse[1]) + df:coord mousePos = Gui::getMousePos(); + if(mousePos.x != last_mouse[0] || mousePos.y != last_mouse[1]) { - last_mouse[0] = mouse_x; - last_mouse[1] = mouse_y; - out.print("Mouse: %d %d\n",mouse_x, mouse_y); + last_mouse[0] = mousePos.x; + last_mouse[1] = mousePos.y; + out.print("Mouse: %d %d\n",mousePos.x, mousePos.y); } } return CR_OK; diff --git a/plugins/mousequery.cpp b/plugins/mousequery.cpp index cd85de2bc..2bfa08470 100644 --- a/plugins/mousequery.cpp +++ b/plugins/mousequery.cpp @@ -53,23 +53,11 @@ static enum { None, Left, Right } drag_mode; static df::coord get_mouse_pos(int32_t &mx, int32_t &my) { - df::coord pos; - pos.x = -30000; - - if (!enabler->tracking_on) - return pos; - - if (!Gui::getMousePos(mx, my)) - return pos; - - int32_t vx, vy, vz; - if (!Gui::getViewCoords(vx, vy, vz)) - return pos; - - pos.x = vx + mx - 1; - pos.y = vy + my - 1; - pos.z = vz - Gui::getDepthAt(mx, my); + df::coord pos = Gui::getMousePos(); + pos.z -= Gui::getDepthAt(pos.x, pos.y); + mx = pos.x; + my = pos.y; return pos; } From 1f15e4d47ec593cc5bc803d6e5bdec720c48b07b Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 18 Sep 2022 08:19:56 -0700 Subject: [PATCH 114/121] expose Gui::getMousePos() to Lua --- library/LuaApi.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 70cb01dfc..b556e5737 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1523,6 +1523,12 @@ static int gui_getDwarfmodeViewDims(lua_State *state) return 1; } +static int gui_getMousePos(lua_State *L) +{ + Lua::PushPosXYZ(L, Gui::getMousePos()); + return 3; +} + static const LuaWrapper::FunctionReg dfhack_gui_module[] = { WRAPM(Gui, getCurViewscreen), WRAPM(Gui, getFocusString), @@ -1555,6 +1561,7 @@ static const LuaWrapper::FunctionReg dfhack_gui_module[] = { static const luaL_Reg dfhack_gui_funcs[] = { { "getDwarfmodeViewDims", gui_getDwarfmodeViewDims }, + { "getMousePos", gui_getMousePos }, { NULL, NULL } }; @@ -2282,9 +2289,7 @@ static const LuaWrapper::FunctionReg dfhack_screen_module[] = { static int screen_getMousePos(lua_State *L) { - auto pos = Screen::getMousePos(); - lua_pushinteger(L, pos.x); - lua_pushinteger(L, pos.y); + Lua::PushPosXY(L, Screen::getMousePos()); return 2; } From 3b650c8d0bb5cd31be1bbffcab7bef34ac4452dc Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 18 Sep 2022 08:20:33 -0700 Subject: [PATCH 115/121] don't let changes to the lua cursor bleed through before, we could end up changing the state of whoever last called guidm.setCursorPos() --- library/lua/gui/dwarfmode.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index e56211233..db80554cd 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -104,7 +104,7 @@ function getCursorPos() end function setCursorPos(cursor) - df.global.cursor = cursor + df.global.cursor = copyall(cursor) end function clearCursorPos() From 8fd86f6e566d6fa855650d49ac489e815ca6cd71 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 18 Sep 2022 08:40:22 -0700 Subject: [PATCH 116/121] fix typo in kittens.cpp --- plugins/devel/kittens.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/devel/kittens.cpp b/plugins/devel/kittens.cpp index 56ca813ae..156686a1e 100644 --- a/plugins/devel/kittens.cpp +++ b/plugins/devel/kittens.cpp @@ -135,7 +135,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) last_designation[2] = desig_z; out.print("Designation: %d %d %d\n",desig_x, desig_y, desig_z); } - df:coord mousePos = Gui::getMousePos(); + df::coord mousePos = Gui::getMousePos(); if(mousePos.x != last_mouse[0] || mousePos.y != last_mouse[1]) { last_mouse[0] = mousePos.x; From dbc5001bd8947731b676742f1329830eba1a6db0 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 18 Sep 2022 13:27:13 -0700 Subject: [PATCH 117/121] fail if mouse cursor isn't over the map area --- library/modules/Gui.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index f9e15aac4..56aa09b7f 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -1836,6 +1836,12 @@ df::coord Gui::getMousePos() { df::coord pos; if (gps && gps->mouse_x > -1) { + // return invalid coords if the cursor is not over the map + DwarfmodeDims dims = getDwarfmodeViewDims(); + if (gps->mouse_x < dims.map_x1 || gps->mouse_x > dims.map_x2 || + gps->mouse_y < dims.map_y1 || gps->mouse_y > dims.map_y2) { + return pos; + } pos = getViewportPos(); pos.x += gps->mouse_x - 1; pos.y += gps->mouse_y - 1; From 28b70eaee9d2d1b2b9d78ecd4d19bac13c8ba01b Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 18 Sep 2022 13:27:33 -0700 Subject: [PATCH 118/121] return map coords as a coord, not x, y, z --- library/LuaApi.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index b556e5737..3a83f7aef 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1525,8 +1525,12 @@ static int gui_getDwarfmodeViewDims(lua_State *state) static int gui_getMousePos(lua_State *L) { - Lua::PushPosXYZ(L, Gui::getMousePos()); - return 3; + auto pos = Gui::getMousePos(); + if (pos.isValid()) + Lua::Push(L, pos); + else + lua_pushnil(L); + return 1; } static const LuaWrapper::FunctionReg dfhack_gui_module[] = { @@ -2289,16 +2293,12 @@ static const LuaWrapper::FunctionReg dfhack_screen_module[] = { static int screen_getMousePos(lua_State *L) { - Lua::PushPosXY(L, Screen::getMousePos()); - return 2; + return Lua::PushPosXY(L, Screen::getMousePos()); } static int screen_getWindowSize(lua_State *L) { - auto pos = Screen::getWindowSize(); - lua_pushinteger(L, pos.x); - lua_pushinteger(L, pos.y); - return 2; + return Lua::PushPosXY(L, Screen::getWindowSize()); } static int screen_paintTile(lua_State *L) From 8e718d9851600c7247b6e1c8f95db39e6f59b3b6 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 18 Sep 2022 13:31:20 -0700 Subject: [PATCH 119/121] add docs for dfhack.gui.getMousePos() --- docs/Lua API.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index ba63030b5..64ccdd595 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1088,6 +1088,11 @@ Announcements Uses the type to look up options from announcements.txt, and calls the above operations accordingly. The units are used to call ``addCombatReportAuto``. +* ``dfhack.gui.getMousePos()`` + + Returns the map coordinates of the map tile the mouse is over as a table of + ``{x, y, z}``. If the cursor is not over the map, returns ``nil``. + Other ~~~~~ From 24816763703f9179bf481602fbf7afc1962463a0 Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 19 Sep 2022 11:13:47 -0700 Subject: [PATCH 120/121] fix mouse pos offset and output 'X' at screen edge --- plugins/mousequery.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/plugins/mousequery.cpp b/plugins/mousequery.cpp index 2bfa08470..6358262f4 100644 --- a/plugins/mousequery.cpp +++ b/plugins/mousequery.cpp @@ -20,6 +20,7 @@ #include "uicommon.h" #include "TileTypes.h" #include "DataFuncs.h" +#include "Debug.h" DFHACK_PLUGIN("mousequery"); REQUIRE_GLOBAL(enabler); @@ -32,6 +33,10 @@ using namespace df::enums::ui_sidebar_mode; #define PLUGIN_VERSION 0.18 +namespace DFHack { + DBG_DECLARE(mousequery,log,DebugCategory::LINFO); +} + static int32_t last_clicked_x, last_clicked_y, last_clicked_z; static int32_t last_pos_x, last_pos_y, last_pos_z; static df::coord last_move_pos; @@ -56,8 +61,10 @@ static df::coord get_mouse_pos(int32_t &mx, int32_t &my) df::coord pos = Gui::getMousePos(); pos.z -= Gui::getDepthAt(pos.x, pos.y); - mx = pos.x; - my = pos.y; + df::coord vpos = Gui::getViewportPos(); + mx = pos.x - vpos.x + 1; + my = pos.y - vpos.y + 1; + return pos; } @@ -524,6 +531,7 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest if (mpos.x == x && mpos.y == y && mpos.z == z) return; + DEBUG(log).print("moving cursor to %d, %d, %d\n", x, y, z); Gui::setCursorCoords(mpos.x, mpos.y, mpos.z); Gui::refreshSidebar(); } @@ -563,8 +571,6 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest int32_t mx, my; auto mpos = get_mouse_pos(mx, my); bool mpos_valid = mpos.x != -30000 && mpos.y != -30000 && mpos.z != -30000; - if (mx < 1 || mx > dims.map_x2 || my < 1 || my > dims.map_y2) - mpos_valid = false; // Check if in lever binding mode if (Gui::getFocusString(Core::getTopViewscreen()) == @@ -711,7 +717,7 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest } } - OutputString(color, mx, my, "X", false, 0, 0, true); + Screen::paintTile(Screen::Pen('X', color), mx, my, true); return; } From a5af7020eb910e9d06e1c9f115320e1f5fcf80b0 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Tue, 20 Sep 2022 07:28:37 +0000 Subject: [PATCH 121/121] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index a422b1bf7..484988bb3 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit a422b1bf7ff99d578b59540108fe74eca38606d2 +Subproject commit 484988bb3d64f7aed9442af3b226faa77d8fd00d