diff --git a/.gitignore b/.gitignore index db4ed747c..9ff16b94c 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ build/VC2010 # Sphinx generated documentation docs/_* docs/html/ +docs/pdf/ # in-place build build/Makefile diff --git a/conf.py b/conf.py index a46039126..d6c40b0a7 100644 --- a/conf.py +++ b/conf.py @@ -359,3 +359,5 @@ latex_documents = [ (master_doc, 'DFHack.tex', 'DFHack Documentation', 'The DFHack Team', 'manual'), ] + +latex_toplevel_sectioning = 'part' diff --git a/docs/Documentation.rst b/docs/Documentation.rst index 462932b0f..37fcc6ee6 100644 --- a/docs/Documentation.rst +++ b/docs/Documentation.rst @@ -236,9 +236,9 @@ Running Sphinx manually ----------------------- You can also build the documentation without going through CMake, which may be -faster. There is a ``docs/build.sh`` script available for Linux and macOS that +faster. There is a ``docs/build.sh`` script provided for Linux and macOS that will run essentially the same command that CMake runs - see the script for -options. +additional options. To build the documentation with default options, run the following command from the root DFHack folder:: @@ -248,6 +248,19 @@ the root DFHack folder:: Sphinx has many options to enable clean builds, parallel builds, logging, and more - run ``sphinx-build --help`` for details. +Building a PDF version +---------------------- + +ReadTheDocs automatically builds a PDF version of the documentation (available +under the "Downloads" section when clicking on the release selector). If you +want to build a PDF version locally, you will need ``pdflatex``, which is part +of a TeX distribution. The following command will then build a PDF, located in +``docs/pdf/latex/DFHack.pdf``, with default options:: + + sphinx-build -M latexpdf . ./docs/pdf + +There is a ``docs/build-pdf.sh`` script provided for Linux and macOS that runs +this command for convenience - see the script for additional options. .. _build-changelog: diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 4c7163623..2cfaff350 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -158,7 +158,9 @@ that don't fit any of the other reference types. Such references can only appear as a value of a pointer field, or as a result of calling the ``_field()`` method. -They behave as structs with one field ``value`` of the right type. +They behave as structs with a ``value`` field of the right type. If the +object's XML definition has a ``ref-target`` attribute, they will also have +a read-only ``ref_target`` field set to the corresponding type object. To make working with numeric buffers easier, they also allow numeric indices. Note that other than excluding negative values diff --git a/docs/build-pdf.sh b/docs/build-pdf.sh new file mode 100755 index 000000000..76908b49b --- /dev/null +++ b/docs/build-pdf.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +# usage: +# ./build-pdf.sh +# SPHINX=/path/to/sphinx-build ./build-pdf.sh +# JOBS=3 ./build-pdf.sh ... +# all command-line arguments are passed directly to sphinx-build - run +# ``sphinx-build --help`` for a list, or see +# https://www.sphinx-doc.org/en/master/man/sphinx-build.html + +cd $(dirname "$0") +cd .. + +sphinx=sphinx-build +if [ -n "$SPHINX" ]; then + sphinx=$SPHINX +fi + +if [ -z "$JOBS" ]; then + JOBS=2 +fi + +"$sphinx" -M latexpdf . ./docs/pdf -w ./docs/_sphinx-warnings.txt -j "$JOBS" "$@" diff --git a/docs/build.sh b/docs/build.sh index 48e1bfd4a..95a97e539 100755 --- a/docs/build.sh +++ b/docs/build.sh @@ -1,9 +1,12 @@ #!/bin/sh # usage: -# ./build.sh -# ./build.sh sphinx-executable -# JOBS=3 ./build.sh ... +# ./build.sh +# SPHINX=/path/to/sphinx-build ./build.sh +# JOBS=3 ./build.sh ... +# all command-line arguments are passed directly to sphinx-build - run +# ``sphinx-build --help`` for a list, or see +# https://www.sphinx-doc.org/en/master/man/sphinx-build.html cd $(dirname "$0") cd .. @@ -17,4 +20,4 @@ if [ -z "$JOBS" ]; then JOBS=2 fi -"$sphinx" -a -E -b html . ./docs/html -w ./docs/_sphinx-warnings.txt -j "$JOBS" "$@" +"$sphinx" -a -b html . ./docs/html -w ./docs/_sphinx-warnings.txt -j "$JOBS" "$@" diff --git a/docs/changelog.txt b/docs/changelog.txt index e43abd188..8ce2fa683 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -37,6 +37,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - Fixed a segfault when attempting to start a headless session with a graphical PRINT_MODE setting - Fixed an issue with the macOS launcher failing to un-quarantine some files - `labormanager`: fixed handling of new jobs in 0.47 +- `embark-assistant`: fixed a couple of incursion handling bugs. - Fixed ``Units::isEggLayer``, ``Units::isGrazer``, ``Units::isMilkable``, ``Units::isTrainableHunting``, ``Units::isTrainableWar``, and ``Units::isTamable`` ignoring the unit's caste - `RemoteFortressReader`: fixed a couple crashes that could result from decoding invalid enum items (``site_realization_building_type`` and ``improvement_type``) @@ -45,6 +46,9 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `blueprint`: now automatically creates folder trees when organizing blueprints into subfolders (e.g. ``blueprint 30 30 1 rooms/dining dig`` will create the file ``blueprints/rooms/dining-dig.csv``); previously it would fail if the ``blueprints/rooms/`` directory didn't already exist - `confirm`: added a confirmation dialog for convicting dwarves of crimes +## Lua +- Added a ``ref_target`` field to primitive field references, corresponding to the ``ref-target`` XML attribute + ## Ruby - Updated ``item_find`` and ``building_find`` to use centralized logic that works on more screens diff --git a/docs/styles/dfhack.css b/docs/styles/dfhack.css index daedd1120..a25772871 100644 --- a/docs/styles/dfhack.css +++ b/docs/styles/dfhack.css @@ -56,3 +56,8 @@ div.sphinxsidebar h3.logo-name a { div.body { min-width: unset; } + +div.body li > p { + margin-top: 0; + margin-bottom: 0; +} diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index c46d7ce05..845d273e3 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -536,6 +536,7 @@ static void field_reference(lua_State *state, const struct_field_info *field, vo case struct_field_info::PRIMITIVE: case struct_field_info::SUBSTRUCT: push_object_internal(state, field->type, ptr); + get_object_ref_header(state, -1)->field_info = field; return; case struct_field_info::POINTER: @@ -706,6 +707,17 @@ static type_identity *find_primitive_field(lua_State *state, int field, const ch */ static int meta_primitive_index(lua_State *state) { + const char *attr = lua_tostring(state, -1); + if (strcmp(attr, "ref_target") == 0) { + const struct_field_info *field_info = get_object_ref_header(state, 1)->field_info; + if (field_info && field_info->extra && field_info->extra->ref_target) { + LookupInTable(state, field_info->extra->ref_target, &DFHACK_TYPEID_TABLE_TOKEN); + } else { + lua_pushnil(state); + } + return 1; + } + uint8_t *ptr = get_object_addr(state, 1, 2, "read"); auto type = find_primitive_field(state, 2, "read", &ptr); if (!type) @@ -1304,6 +1316,8 @@ static void MakePrimitiveMetatable(lua_State *state, type_identity *type) { EnableMetaField(state, base+2, "value", type); AssociateId(state, base+3, 1, "value"); + + EnableMetaField(state, base+2, "ref_target", NULL); } // Add the iteration metamethods diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp index 497addd5c..05c4db789 100644 --- a/library/LuaWrapper.cpp +++ b/library/LuaWrapper.cpp @@ -170,18 +170,24 @@ void LuaWrapper::push_object_ref(lua_State *state, void *ptr) // stack: [metatable] auto ref = (DFRefHeader*)lua_newuserdata(state, sizeof(DFRefHeader)); ref->ptr = ptr; + ref->field_info = NULL; lua_swap(state); lua_setmetatable(state, -2); // stack: [userdata] } -void *LuaWrapper::get_object_ref(lua_State *state, int val_index) +DFRefHeader *LuaWrapper::get_object_ref_header(lua_State *state, int val_index) { assert(!lua_islightuserdata(state, val_index)); auto ref = (DFRefHeader*)lua_touserdata(state, val_index); - return ref->ptr; + return ref; +} + +void *LuaWrapper::get_object_ref(lua_State *state, int val_index) +{ + return get_object_ref_header(state, val_index)->ptr; } /** diff --git a/library/include/LuaWrapper.h b/library/include/LuaWrapper.h index 4cbf9d34f..b23e1f912 100644 --- a/library/include/LuaWrapper.h +++ b/library/include/LuaWrapper.h @@ -126,6 +126,7 @@ namespace LuaWrapper { */ struct DFRefHeader { void *ptr; + const struct_field_info *field_info; }; /** @@ -133,15 +134,7 @@ namespace LuaWrapper { */ void push_object_ref(lua_State *state, void *ptr); DFHACK_EXPORT void *get_object_ref(lua_State *state, int val_index); - - /* - * The system might be extended to carry some simple - * objects inline inside the reference buffer. - */ - inline bool is_self_contained(DFRefHeader *ptr) { - void **pp = &ptr->ptr; - return **(void****)pp == (pp + 1); - } + DFHACK_EXPORT DFRefHeader *get_object_ref_header(lua_State *state, int val_index); /** * Report an error while accessing a field (index = field name). diff --git a/library/xml b/library/xml index b9028b0bb..2c21bf250 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit b9028b0bb9ad40d3ad4dc3f10934bb61aa16629b +Subproject commit 2c21bf2503751fae87cfd62e9608003b5f0dc1c5 diff --git a/plugins/embark-assistant/defs.h b/plugins/embark-assistant/defs.h index 29c5a063c..13494b91e 100644 --- a/plugins/embark-assistant/defs.h +++ b/plugins/embark-assistant/defs.h @@ -119,6 +119,8 @@ namespace embark_assist { df::world_region_type region_type[16][16]; // Required for incursion override detection. We could store only the // edges, but storing it for every tile allows for a unified fetching // logic. + int8_t north_row_biome_x[16]; // "biome_x" data cached for the northern row for access from the north. + int8_t west_column_biome_y[16]; // "biome_y" data cached for the western row for access from the west. }; struct geo_datum { diff --git a/plugins/embark-assistant/survey.cpp b/plugins/embark-assistant/survey.cpp index 79c66e4df..2d5dca04d 100644 --- a/plugins/embark-assistant/survey.cpp +++ b/plugins/embark-assistant/survey.cpp @@ -1470,6 +1470,8 @@ void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data tile->north_corner_selection[i] = world_data->region_details[0]->edges.biome_corner[i][0]; tile->west_corner_selection[i] = world_data->region_details[0]->edges.biome_corner[0][i]; + tile->north_row_biome_x[i] = world_data->region_details[0]->edges.biome_x[i][0]; + tile->west_column_biome_y[i] = world_data->region_details[0]->edges.biome_y[0][i]; } for (uint8_t i = 0; i < 16; i++) { @@ -1601,7 +1603,7 @@ df::world_region_type embark_assist::survey::region_type_of(embark_assist::defs: int16_t effective_y = y; int8_t effective_i = i; int8_t effective_k = k; - adjust_coordinates(&effective_x, &effective_y, &effective_i, &effective_i); + adjust_coordinates(&effective_x, &effective_y, &effective_i, &effective_k); if (effective_x < 0 || effective_x >= world_data->world_width || @@ -1657,7 +1659,7 @@ uint8_t embark_assist::survey::translate_corner(embark_assist::defs::world_tile effective_k = k + 1; } - adjust_coordinates(&effective_x, &effective_y, &effective_i, &effective_i); + adjust_coordinates(&effective_x, &effective_y, &effective_i, &effective_k); if (effective_x == world_data->world_width) { if (effective_y == world_data->world_height) { // Only the SE corner of the SE most tile of the world can reference this. @@ -1919,7 +1921,17 @@ uint8_t embark_assist::survey::translate_ns_edge(embark_assist::defs::world_tile north_region_type = embark_assist::survey::region_type_of(survey_results, x, y, i, k - 1); } else { - effective_edge = world_data->region_details[0]->edges.biome_x[i][k + 1]; + if (k < 15) { // We're still within the same world tile + effective_edge = world_data->region_details[0]->edges.biome_x[i][k + 1]; + } + else { // Getting the data from the world tile to the south + if (y + 1 == world_data->world_height) { + return 4; // There's nothing to the south, so we fall back on our own tile. + } + + effective_edge = survey_results->at(x).at(y + 1).north_row_biome_x[i]; + } + north_region_type = embark_assist::survey::region_type_of(survey_results, x, y, i, k); south_region_type = embark_assist::survey::region_type_of(survey_results, x, y, i, k + 1); } @@ -1993,7 +2005,16 @@ uint8_t embark_assist::survey::translate_ew_edge(embark_assist::defs::world_tile west_region_type = embark_assist::survey::region_type_of(survey_results, x, y, i - 1, k); } else { - effective_edge = world_data->region_details[0]->edges.biome_y[i + 1][k]; + if (i < 15) { // We're still within the same world tile + effective_edge = world_data->region_details[0]->edges.biome_y[i + 1][k]; + } + else { // Getting the data from the world tile to the east + if (x + 1 == world_data->world_width) { + return 4; // There's nothing to the east, so we fall back on our own tile. + } + + effective_edge = survey_results->at(x + 1).at(y).west_column_biome_y[k]; + } west_region_type = embark_assist::survey::region_type_of(survey_results, x, y, i, k); east_region_type = embark_assist::survey::region_type_of(survey_results, x, y, i + 1, k); } @@ -2424,7 +2445,7 @@ void embark_assist::survey::survey_embark(embark_assist::defs::mid_level_tiles * } else { process_embark_incursion_mid_level_tile - (translate_ns_edge(survey_results, + (translate_ew_edge(survey_results, true, x, y, @@ -2481,7 +2502,7 @@ void embark_assist::survey::survey_embark(embark_assist::defs::mid_level_tiles * } else { process_embark_incursion_mid_level_tile - (translate_ns_edge(survey_results, + (translate_ew_edge(survey_results, false, x, y, diff --git a/scripts b/scripts index e8de92efb..af749e708 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit e8de92efb73d5ef4d0b52df000d60d3350f07a37 +Subproject commit af749e7086739a058cd5095a6ee1a60d7e795a7c diff --git a/test/main.lua b/test/main.lua index 46a91e1ca..83a35d165 100644 --- a/test/main.lua +++ b/test/main.lua @@ -69,6 +69,19 @@ function expect.error(func, ...) return true end end +function expect.error_match(func, matcher, ...) + local ok, err = pcall(func, ...) + if ok then + return false, 'no error raised by function call' + elseif type(matcher) == 'string' then + if not tostring(err):match(matcher) then + return false, ('error "%s" did not match "%s"'):format(err, matcher) + end + elseif not matcher(err) then + return false, ('error "%s" did not satisfy matcher'):format(err) + end + return true +end function expect.pairs_contains(table, key, comment) for k, v in pairs(table) do if k == key then diff --git a/test/structures/ref_target.lua b/test/structures/ref_target.lua new file mode 100644 index 000000000..05818ee06 --- /dev/null +++ b/test/structures/ref_target.lua @@ -0,0 +1,35 @@ +function test.get() + dfhack.with_temp_object(df.unit:new(), function(unit) + expect.eq(unit:_field('hist_figure_id').ref_target, df.historical_figure) + end) +end + +function test.get_nil() + dfhack.with_temp_object(df.coord:new(), function(coord) + expect.nil_(coord:_field('x').ref_target) + end) +end + +function test.get_non_primitive() + dfhack.with_temp_object(df.unit:new(), function(unit) + expect.error_match(function() + return unit:_field('status').ref_target + end, 'not found') + end) +end + +function test.set() + dfhack.with_temp_object(df.unit:new(), function(unit) + expect.error_match(function() + unit:_field('hist_figure_id').ref_target = df.coord + end, 'builtin property or method') + end) +end + +function test.set_non_primitive() + dfhack.with_temp_object(df.unit:new(), function(unit) + expect.error_match(function() + unit:_field('status').ref_target = df.coord + end, 'not found') + end) +end