Skip to content

Commit 36fa162

Browse files
authored
Merge branch 'develop' into random
2 parents c0b1bf8 + ee319b1 commit 36fa162

File tree

12 files changed

+200
-60
lines changed

12 files changed

+200
-60
lines changed

data/art/design.png

241 Bytes
Loading

docs/about/Authors.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ DoctorVanGogh DoctorVanGogh
6363
Donald Ruegsegger hashaash
6464
doomchild doomchild
6565
Droseran Droseran
66+
dvantwisk dvantwisk
6667
DwarvenM DwarvenM
6768
Eamon Bode eamondo2 Baron Von Munchhausen
6869
EarthPulseAcademy EarthPulseAcademy

docs/changelog.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,19 @@ Template for new versions:
5959
- `getplants`: will no longer crash when faced with plants with growths that do not drop seeds when processed
6060
- `getplants`: use updated formula for calculating whether plant growths are ripe
6161
- `getplants`: fix logic for determining whether plant growths have been picked
62+
- `gui/teleport`: adapt to new behavior in DF 51.11 to avoid a crash when teleporting items into mid-air
6263

6364
## Misc Improvements
65+
- All places where units are listed in DFHack tools now show the translated English name in addition to the native name. In particular, this makes units searchable by English name in `gui/sitemap`.
6466

6567
## Documentation
6668

6769
## API
6870
- ``Random`` module: added SplitmixRNG class, implements the Splitmix64 RNG used by Dwarf Fortress for "simple" randomness
6971

7072
## Lua
73+
- ``script-manager``: new ``get_active_mods()`` function for getting information on active mods
74+
- ``script-manager``: new ``get_mod_info_metadata()`` function for getting information out of mod ``info.txt`` files
7175

7276
## Removed
7377

@@ -81,6 +85,7 @@ Template for new versions:
8185
## Fixes
8286
- `preserve-rooms`: will no longer crash when a civzone is assigned to a unit that does not exist
8387
- `gui/design`: fix misaligned shape icons
88+
- `script-manager`: fix lua scripts in mods not being reloaded properly upon entering a saved world on Windows.
8489

8590
# 51.11-r1
8691

@@ -104,6 +109,7 @@ Template for new versions:
104109
- ``Buildings`` module: add ``getOwner`` (using the ``Units::get_cached_unit_by_global_id`` mechanic) to reflect changes in 51.11
105110
- ``Units::teleport``: projectile information is now cleared for teleported units
106111
- ``Buildings::setOwner``: updated for changes in 51.11
112+
- ``Items::getDescription``: fixed display of quality levels, now displays ALL item designations (in correct order) and obeys SHOW_IMP_QUALITY
107113

108114
## Lua
109115
- ``dfhack.military.addToSquad``: expose Military API function

docs/dev/Lua API.rst

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1773,17 +1773,18 @@ Units module
17731773
Get human-readable baby or child name (e.g., "dwarven baby" or
17741774
"dwarven child").
17751775

1776-
* ``dfhack.units.getReadableName(unit or historical_figure)``
1776+
* ``dfhack.units.getReadableName(unit or historical_figure[, skip_english])``
17771777

1778-
Returns a string that includes the language name of the unit (if any), the
1779-
race of the unit (if different from fort), whether it is trained for war or
1780-
hunting, any syndrome-given descriptions (such as "necromancer"), the
1781-
training level (if tame), and profession or noble role. If a
1782-
``historical_figure`` is passed instead of a unit, some information
1783-
(e.g., agitation status) is not available, and the profession may be
1784-
different (e.g., "Monk") from what is displayed in fort mode.
1778+
Returns a string that includes the native and english language name (if
1779+
``skip_english`` is not ``true``) of the unit (if any), the race of the unit
1780+
(if different from fort), whether it is trained for war or hunting, any
1781+
syndrome-given descriptions (such as "necromancer"), the training level (if
1782+
tame), and profession or noble role. If a ``historical_figure`` is passed
1783+
instead of a unit, some information (e.g., agitation status) is not
1784+
available, and the profession may be different (e.g., "Monk") from what is
1785+
displayed in fort mode.
17851786

1786-
* ``dfhack.units.getAge(unit[,true_age])``
1787+
* ``dfhack.units.getAge(unit[, true_age])``
17871788

17881789
Returns the age of the unit in years as a floating-point value.
17891790
If ``true_age`` is true, ignores false identities.
@@ -3338,12 +3339,14 @@ and are only documented here for completeness:
33383339

33393340
* ``dfhack.internal.findScript(name)``
33403341

3341-
Searches `script paths <script-paths>` for the script ``name`` and returns the
3342-
path of the first file found, or ``nil`` on failure.
3342+
Searches `script paths <script-paths>` for the script ``name`` (which
3343+
includes the ``.lua`` extension) and returns the absolute path of the first
3344+
file found, or ``nil`` on failure. Slashes in the path are canonicalized to
3345+
forward slashes.
33433346

33443347
.. note::
3345-
This requires an extension to be specified (``.lua`` or ``.rb``) - use
3346-
``dfhack.findScript()`` to include the ``.lua`` extension automatically.
3348+
You can use the ``dfhack.findScript()`` wrapper if you want to specify the
3349+
script name without the ``.lua`` extension.
33473350

33483351
* ``dfhack.internal.runCommand(command[, use_console])``
33493352

@@ -3783,6 +3786,29 @@ paths will be relative to the top level game directory and will end in a slash
37833786
Which would open ``dfhack-config/mods/my_awesome_mod/settings.json``. After
37843787
calling ``getModStatePath``, the returned directory is guaranteed to exist.
37853788

3789+
* ``get_active_mods()``
3790+
3791+
Returns a list of all active mods in the current world. The list elements are
3792+
tables containing the following fields:
3793+
3794+
- id: mod id
3795+
- name: mod display name
3796+
- version: mod display version
3797+
- numeric_version: numeric mod version
3798+
- path: path to the mod directory
3799+
- vanilla: true if this is a vanilla mod
3800+
3801+
* ``get_mod_info_metadata(mod_path, tags)``
3802+
3803+
Returns a table with the values of the given tags from the ``info.txt`` file
3804+
in the given mod directory. The ``mod_path`` argument must be a path to a mod
3805+
directory (retrieved, say, from ``get_active_mods()``). The ``tags`` argument
3806+
is a string or a list of strings representing the tags to retrieve. The
3807+
function will return a table with the tag names as keys and their values as
3808+
values. If a requested tag includes the string ``NUMERIC_``, it will return
3809+
the numeric value for that tag (e.g., ``NUMERIC_VERSION`` will return the
3810+
numeric version of the mod as a number instead of a string).
3811+
37863812
utils
37873813
=====
37883814

library/LuaApi.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2291,10 +2291,11 @@ static int units_assignTrainer(lua_State *L) {
22912291
}
22922292

22932293
static int units_getReadablename(lua_State *L) {
2294+
bool skip_english = lua_toboolean(L, 2); // defaults to false
22942295
if (auto unit = Lua::GetDFObject<df::unit>(L, 1))
2295-
Lua::Push(L, Units::getReadableName(unit));
2296+
Lua::Push(L, Units::getReadableName(unit, skip_english));
22962297
else if (auto hf = Lua::GetDFObject<df::historical_figure>(L, 1))
2297-
Lua::Push(L, Units::getReadableName(hf));
2298+
Lua::Push(L, Units::getReadableName(hf, skip_english));
22982299
else
22992300
luaL_argerror(L, 1, "Expected a unit or a historical figure");
23002301
return 1;
@@ -3965,7 +3966,7 @@ static int internal_findScript(lua_State *L)
39653966
const char *name = luaL_checkstring(L, 1);
39663967
std::filesystem::path path = Core::getInstance().findScript(name);
39673968
if (!path.empty())
3968-
lua_pushstring(L, path.string().c_str());
3969+
lua_pushstring(L, Filesystem::as_string(path).c_str());
39693970
else
39703971
lua_pushnil(L);
39713972
return 1;

library/include/modules/Units.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -281,9 +281,9 @@ DFHACK_EXPORT std::string getRaceBabyName(df::unit *unit, bool plural = false);
281281
DFHACK_EXPORT std::string getRaceChildNameById(int32_t race_id, bool plural = false);
282282
DFHACK_EXPORT std::string getRaceChildName(df::unit *unit, bool plural = false);
283283
// Full readable name including profession. HF profession might be different from unit profession.
284-
DFHACK_EXPORT std::string getReadableName(df::historical_figure *hf);
284+
DFHACK_EXPORT std::string getReadableName(df::historical_figure *hf, bool skip_english = false);
285285
// Full readable name including profession, curse name, and tame description.
286-
DFHACK_EXPORT std::string getReadableName(df::unit *unit);
286+
DFHACK_EXPORT std::string getReadableName(df::unit *unit, bool skip_english = false);
287287

288288
// Unit's age (in non-integer years). Ignore false identities if true_age.
289289
DFHACK_EXPORT double getAge(df::unit *unit, bool true_age = false);

library/lua/dfhack.lua

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -994,13 +994,13 @@ local valid_script_flags = {
994994
scripts = {required = false},
995995
}
996996

997-
local warned_scripts = {}
997+
local checked_scripts = {}
998998

999999
function dfhack.run_script(name,...)
1000-
if not warned_scripts[name] then
1000+
if not checked_scripts[name] then
1001+
checked_scripts[name] = true
10011002
local helpdb = require('helpdb')
10021003
if helpdb.has_tag(name, 'unavailable') then
1003-
warned_scripts[name] = true
10041004
dfhack.printerr(('UNTESTED WARNING: the "%s" script has not been validated to work well with this version of DF.'):format(name))
10051005
dfhack.printerr('It may not work as expected, or it may corrupt your game.')
10061006
qerror('Please run the command again to ignore this warning and proceed.')

library/lua/script-manager.lua

Lines changed: 69 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -88,25 +88,52 @@ local INSTALLED_MODS_PATH = 'data/installed_mods/'
8888
-- changes to the files)
8989
local MOD_PATH_ROOTS = {WORKSHOP_MODS_PATH, MODS_PATH, INSTALLED_MODS_PATH}
9090

91-
local function get_mod_id_and_version(path)
92-
local idfile = path .. '/info.txt'
91+
-- returns the values of the given list of tags from the info.txt file in the given mod directory
92+
-- if a requested tag includes the string 'NUMERIC_', it will return the numeric value for that tag
93+
-- (e.g. NUMERIC_VERSION will return the numeric version of the mod as a number instead of a string).
94+
function get_mod_info_metadata(mod_path, tags)
95+
local idfile = mod_path .. '/info.txt'
9396
local ok, lines = pcall(io.lines, idfile)
9497
if not ok then return end
95-
local id, version
98+
99+
if type(tags) ~= 'table' then
100+
tags = {tags}
101+
end
102+
103+
local tag_regexes = {}
104+
for _,tag in ipairs(tags) do
105+
local tag_regex = ('^%%[%s:'):format(tag)
106+
if tag:find('NUMERIC_') then
107+
-- note this doesn't go all the way to the closing brace since some people put
108+
-- non-number characters in here, and DF only reads the leading digits for
109+
-- numeric fields
110+
tag_regex = tag_regex .. '(%d+)'
111+
else
112+
tag_regex = tag_regex .. '([^%]]+)'
113+
end
114+
tag_regexes[tag] = tag_regex
115+
end
116+
117+
local values = {}
96118
for line in lines do
97-
if not id then
98-
_,_,id = line:find('^%[ID:([^%]]+)%]')
119+
local _,_,info_tag = line:find('^%[([^:]+):')
120+
if not info_tag or not tag_regexes[info_tag] then
121+
goto continue
99122
end
100-
if not version then
101-
-- note this doesn't include the closing brace since some people put
102-
-- non-number characters in here, and DF only reads the leading digits
103-
-- as the numeric version
104-
_,_,version = line:find('^%[NUMERIC_VERSION:(%d+)')
123+
local _,_,value = line:find(tag_regexes[info_tag])
124+
if value then
125+
values[info_tag] = value
105126
end
106-
-- note that we do *not* want to break out of this loop early since
107-
-- lines has to hit EOF to close the file
127+
::continue::
108128
end
109-
return id, version
129+
130+
return values
131+
end
132+
133+
local function get_mod_id_and_version(path)
134+
local values = get_mod_info_metadata(path, {'ID', 'NUMERIC_VERSION'})
135+
if not values then return end
136+
return values.ID, values.NUMERIC_VERSION
110137
end
111138

112139
local function add_mod_paths(mod_paths, id, base_path, subdir)
@@ -172,6 +199,35 @@ function get_mod_paths(installed_subdir, active_subdir)
172199
return mod_paths
173200
end
174201

202+
-- returns a list of tables in load order with the following fields:
203+
-- id: mod id
204+
-- name: mod display name
205+
-- version: mod display version
206+
-- numeric_version: numeric mod version
207+
-- path: path to the mod directory
208+
-- vanilla: true if this is a vanilla mod
209+
function get_active_mods()
210+
if not dfhack.isWorldLoaded() then return {} end
211+
212+
local mods = {}
213+
214+
local ol = df.global.world.object_loader
215+
216+
for idx=0,#ol.object_load_order_id-1 do
217+
local path = ol.object_load_order_src_dir[idx].value
218+
table.insert(mods, {
219+
id=ol.object_load_order_id[idx].value,
220+
name=ol.object_load_order_name[idx].value,
221+
version=ol.object_load_order_displayed_version[idx].value,
222+
numeric_version=ol.object_load_order_numeric_version[idx],
223+
path=path,
224+
vanilla=path:startswith('data/vanilla/'), -- windows also uses forward slashes
225+
})
226+
end
227+
228+
return mods
229+
end
230+
175231
function get_mod_script_paths()
176232
local paths = {}
177233
for _,v in ipairs(get_mod_paths('scripts_modinstalled', 'scripts_modactive')) do

library/modules/Items.cpp

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ distribution.
4949
#include "df/caravan_state.h"
5050
#include "df/creature_raw.h"
5151
#include "df/dfhack_material_category.h"
52+
#include "df/d_init.h"
5253
#include "df/entity_buy_prices.h"
5354
#include "df/entity_buy_requests.h"
5455
#include "df/entity_raw.h"
@@ -678,14 +679,54 @@ string Items::getDescription(df::item *item, int type, bool decorate) {
678679
item->getItemDescription(&tmp, type);
679680

680681
if (decorate) {
681-
addQuality(tmp, item->getQuality());
682+
// Special indicators get added in a specific order
683+
// Innermost is at the top, and outermost is at the bottom
682684

683-
if (item->isImproved()) {
685+
// First, figure out the quality levels we're going to display
686+
int craftquality = item->getQuality();
687+
int craftquality_only_imps = item->getImprovementQuality();
688+
bool has_displayed_item_improvements = item->isImproved();
689+
if (!has_displayed_item_improvements && (craftquality < craftquality_only_imps))
690+
craftquality = craftquality_only_imps;
691+
692+
// First, actual item quality
693+
addQuality(tmp, craftquality);
694+
695+
// Next, magic enchants
696+
if (item->getMagic() != NULL)
697+
tmp = '\x11' + tmp + '\x10'; // <| |>
698+
699+
// Next, improvements
700+
if (has_displayed_item_improvements) {
684701
tmp = '\xAE' + tmp + '\xAF'; // («) + tmp + (»)
685-
addQuality(tmp, item->getImprovementQuality());
702+
if (df::global::d_init->display.flags.is_set(d_init_flags1::SHOW_IMP_QUALITY))
703+
addQuality(tmp, craftquality_only_imps);
704+
}
705+
706+
// Dwarf mode only, forbid/foreign
707+
if (*df::global::gamemode == game_mode::DWARF) {
708+
if (item->flags.bits.forbid)
709+
tmp = '{' + tmp + '}';
710+
if (item->flags.bits.foreign)
711+
tmp = '(' + tmp + ')';
712+
}
713+
714+
// Wear
715+
switch (item->getWear())
716+
{
717+
case 1: tmp = 'x' + tmp + 'x'; break;
718+
case 2: tmp = 'X' + tmp + 'X'; break;
719+
case 3: tmp = "XX" + tmp + "XX"; break;
686720
}
687-
if (item->flags.bits.foreign)
688-
tmp = "(" + tmp + ")";
721+
722+
// Fire
723+
if (item->flags.bits.on_fire)
724+
tmp = '\x13' + tmp + '\x13'; // !! !!
725+
726+
// Finally, Adventure civzone
727+
if ((*df::global::gamemode == game_mode::ADVENTURE) &&
728+
Items::getGeneralRef(item, general_ref_type::BUILDING_CIVZONE_ASSIGNED) != NULL)
729+
tmp = '$' + tmp + '$';
689730
}
690731
return tmp;
691732
}
@@ -721,13 +762,6 @@ string Items::getReadableDescription(df::item *item) {
721762
CHECK_NULL_POINTER(item);
722763
auto desc = get_base_desc(item);
723764

724-
switch (item->getWear())
725-
{
726-
case 1: desc = "x" + desc + "x"; break; // Worn
727-
case 2: desc = "X" + desc + "X"; break; // Threadbare
728-
case 3: desc = "XX" + desc + "XX"; break; // Tattered
729-
}
730-
731765
if (auto gref = Items::getGeneralRef(item, general_ref_type::CONTAINS_UNIT)) {
732766
if (auto unit = gref->getUnit())
733767
{
@@ -816,9 +850,12 @@ static bool detachItem(df::item *item)
816850
}
817851
}
818852

819-
if (auto ref = virtual_cast<df::general_ref_projectile>(Items::getGeneralRef(item, general_ref_type::PROJECTILE)))
820-
return linked_list_remove(&world->projectiles.all, ref->projectile_id)
821-
&& removeRef(item->general_refs, general_ref_type::PROJECTILE, ref->getID());
853+
if (auto ref = virtual_cast<df::general_ref_projectile>(Items::getGeneralRef(item, general_ref_type::PROJECTILE))) {
854+
auto proj_id = ref->projectile_id;
855+
bool isRefRemoved = removeRef(item->general_refs, general_ref_type::PROJECTILE, proj_id);
856+
bool isLinkRemoved = linked_list_remove(&world->projectiles.all, proj_id);
857+
return isRefRemoved && isLinkRemoved;
858+
}
822859

823860
if (item->flags.bits.on_ground) {
824861
if (!removeItemOnGround(item))

0 commit comments

Comments
 (0)