diff --git a/CMakeLists.txt b/CMakeLists.txt index 44d6a02ce..cd06587eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -184,7 +184,7 @@ endif() # set up versioning. set(DF_VERSION "0.47.04") -set(DFHACK_RELEASE "r1") +set(DFHACK_RELEASE "r2") set(DFHACK_PRERELEASE FALSE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") diff --git a/docs/Authors.rst b/docs/Authors.rst index 31789aa52..42fda0985 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -60,7 +60,6 @@ Ian S kremlin- IndigoFenix James Gilles kazimuth James Logsdon jlogsdon -Japa JapaMala Jared Adams Jeremy Apthorp nornagon Jim Lisi stonetoad @@ -136,12 +135,14 @@ Robert Janetzko robertjanetzko RocheLimit rofl0r rofl0r root +Rose RosaryMala Roses Pheosics Ross M RossM rout rubybrowncoat rubybrowncoat Rumrusher rumrusher RusAnon RusAnon +Ryan Bennitt ryanbennitt sami scamtank scamtank Sebastian Wolfertz Enkrod @@ -172,7 +173,7 @@ U-glouglou\\simon Valentin Ochs Cat-Ion Vitaly Pronkin pronvit mifki ViTuRaS ViTuRaS -Vjek +Vjek vjek Warmist warmist Wes Malone wesQ3 Will Rogers wjrogers diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 8c3763245..45ccd46a9 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1277,6 +1277,30 @@ Units module Retrieves the profession color for the given race/caste using raws. +* ``dfhack.units.getGoalType(unit[,goalIndex])`` + + Retrieves the goal type of the dream that the given unit has. + By default the goal of the first dream is returned. + The goalIndex parameter may be used to retrieve additional dream goals. + Currently only one dream per unit is supported by Dwarf Fortress. + Support for multiple dreams may be added in future versions of Dwarf Fortress. + +* ``dfhack.units.getGoalName(unit[,goalIndex])`` + + Retrieves the short name describing the goal of the dream that the given unit has. + By default the goal of the first dream is returned. + The goalIndex parameter may be used to retrieve additional dream goals. + Currently only one dream per unit is supported by Dwarf Fortress. + Support for multiple dreams may be added in future versions of Dwarf Fortress. + +* ``dfhack.units.isGoalAchieved(unit[,goalIndex])`` + + Checks if given unit has achieved the goal of the dream. + By default the status of the goal of the first dream is returned. + The goalIndex parameter may be used to check additional dream goals. + Currently only one dream per unit is supported by Dwarf Fortress. + Support for multiple dreams may be added in future versions of Dwarf Fortress. + * ``dfhack.units.getStressCategory(unit)`` Returns a number from 0-6 indicating stress. 0 is most stressed; 6 is least. diff --git a/docs/Memory-research.rst b/docs/Memory-research.rst index 41aa437c8..3f615750a 100644 --- a/docs/Memory-research.rst +++ b/docs/Memory-research.rst @@ -86,10 +86,13 @@ You should not count on DF being stable when using this. DFHack's implementation of sizecheck is currently only tested on Linux, although it probably also works on macOS. It can be built with the ``BUILD_SIZECHECK`` `CMake option `, which produces a ``libsizecheck`` -library installed in the ``hack`` folder. You will need to preload this library -manually, by setting ``PRELOAD_LIB`` on Linux (or ``LD_PRELOAD`` if editing -the ``dfhack`` launcher script directly), or by editing the ``dfhack`` -launcher script and adding the library to ``DYLD_INSERT_LIBRARIES`` on macOS. +library installed in the ``hack`` folder. On Linux, passing ``--sc`` as the +first argument to the ``dfhack`` launcher script will load this library on +startup. On other platforms, or when passing a different argument to the +launcher (such as for `linux-gdb`), you will need to preload this library +manually, by setting ``PRELOAD_LIB`` on Linux (or ``LD_PRELOAD`` if editing the +``dfhack`` launcher script directly), or by editing the ``dfhack`` launcher +script and adding the library to ``DYLD_INSERT_LIBRARIES`` on macOS. There is also an older sizecheck implementation by Mifki available on `GitHub `__ (``b.cpp`` is the main @@ -129,6 +132,14 @@ Some basic GDB commands: See the `official GDB documentation `_ for more details. +Other analysis tools +-------------------- + +The ``dfhack`` launcher script on Linux has support for launching several other +tools alongside DFHack, including Valgrind (as well as Callgrind and Helgrind) +and strace. See the script for the exact command-line option to specify. Note +that currently only one tool at a time is supported, and must be specified +with the first argument to the script. df-structures GUI ----------------- diff --git a/docs/changelog.txt b/docs/changelog.txt index e7a01f4fe..9317df86e 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -33,6 +33,8 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: # Future +# 0.47.04-r2 + ## New Tweaks - `tweak` do-job-now: adds a job priority toggle to the jobs list - `tweak` reaction-gloves: adds an option to make reactions produce gloves in sets with correct handedness @@ -43,29 +45,42 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - Linux: fixed ``dfhack.getDFPath()`` (Lua) and ``Process::getPath()`` (C++) to always return the DF root path, even if the working directory has changed - `getplants`: fixed issues causing plants to be collected even if they have no growths (or unripe growths) - `labormanager`: fixed handling of new jobs in 0.47 +- `labormanager`: fixed an issue preventing custom furnaces from being built - `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``) +- `RemoteFortressReader`: fixed an issue that could cause block coordinates to be incorrect +- `rendermax`: fixed a hang that could occur when enabling some renderers, notably on Linux +- `stonesense`: fixed a crash when launching Stonesense +- `stonesense`: fixed some issues that could cause the splash screen to hang ## Misc Improvements +- Linux/macOS: Added console keybindings for deleting words (Alt+Backspace and Alt+d in most terminals) - `blueprint`: now writes blueprints to the ``blueprints/`` subfolder instead of the df root folder - `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 +- `manipulator`: added a new column option to display units' goals ## API - Added ``Filesystem::mkdir_recursive`` - Extended ``Filesystem::listdir_recursive`` to optionally make returned filenames relative to the start directory +- ``Units``: added goal-related functions: ``getGoalType()``, ``getGoalName()``, ``isGoalAchieved()`` ## Internals -- Linux/macOS: Added console keybindings for deleting words (Alt+Backspace and Alt+d in most terminals) - Added support for splitting scripts into multiple files in the ``scripts/internal`` folder without polluting the output of `ls` ## Lua - Added a ``ref_target`` field to primitive field references, corresponding to the ``ref-target`` XML attribute +- Made ``dfhack.units.getRaceNameById()``, ``dfhack.units.getRaceBabyNameById()``, and ``dfhack.units.getRaceChildNameById()`` available to Lua ## Ruby - Updated ``item_find`` and ``building_find`` to use centralized logic that works on more screens +## Documentation +- Expanded the installation guide +- Added some new dev-facing pages, including dedicated pages about the remote API, memory research, and documentation +- Made a couple theme adjustments + # 0.47.04-r1 ## Fixes diff --git a/docs/sphinx_extensions/dfhack/changelog.py b/docs/sphinx_extensions/dfhack/changelog.py index 6f444d36f..d38f8c060 100644 --- a/docs/sphinx_extensions/dfhack/changelog.py +++ b/docs/sphinx_extensions/dfhack/changelog.py @@ -29,6 +29,7 @@ CHANGELOG_SECTIONS = [ 'Lua', 'Ruby', 'Structures', + 'Documentation', ] REPLACEMENTS = { diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 25843875d..78364083f 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1604,6 +1604,9 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { WRAPM(Units, getCasteProfessionName), WRAPM(Units, getProfessionColor), WRAPM(Units, getCasteProfessionColor), + WRAPM(Units, getGoalType), + WRAPM(Units, getGoalName), + WRAPM(Units, isGoalAchieved), WRAPM(Units, getSquadName), WRAPM(Units, isWar), WRAPM(Units, isHunter), @@ -1614,8 +1617,11 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { WRAPM(Units, getPhysicalDescription), WRAPM(Units, getRaceName), WRAPM(Units, getRaceNamePlural), + WRAPM(Units, getRaceNameById), WRAPM(Units, getRaceBabyName), + WRAPM(Units, getRaceBabyNameById), WRAPM(Units, getRaceChildName), + WRAPM(Units, getRaceChildNameById), WRAPM(Units, isBaby), WRAPM(Units, isChild), WRAPM(Units, isAdult), diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index f5995583b..e2d34254d 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -32,6 +32,7 @@ distribution. #include "DataDefs.h" #include "df/caste_raw_flags.h" +#include "df/goal_type.h" #include "df/job_skill.h" #include "df/mental_attribute_type.h" #include "df/misc_trait_type.h" @@ -180,6 +181,10 @@ DFHACK_EXPORT std::string getCasteProfessionName(int race, int caste, df::profes DFHACK_EXPORT int8_t getProfessionColor(df::unit *unit, bool ignore_noble = false); DFHACK_EXPORT int8_t getCasteProfessionColor(int race, int caste, df::profession pid); +DFHACK_EXPORT df::goal_type getGoalType(df::unit *unit, size_t goalIndex = 0); +DFHACK_EXPORT std::string getGoalName(df::unit *unit, size_t goalIndex = 0); +DFHACK_EXPORT bool isGoalAchieved(df::unit *unit, size_t goalIndex = 0); + DFHACK_EXPORT std::string getSquadName(df::unit *unit); DFHACK_EXPORT df::activity_entry *getMainSocialActivity(df::unit *unit); diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index ddcfcd31e..a8f96b29f 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -61,6 +61,7 @@ using namespace std; #include "df/identity_type.h" #include "df/game_mode.h" #include "df/histfig_entity_link_positionst.h" +#include "df/histfig_relationship_type.h" #include "df/historical_entity.h" #include "df/historical_figure.h" #include "df/historical_figure_info.h" @@ -1438,6 +1439,47 @@ int8_t Units::getCasteProfessionColor(int race, int casteid, df::profession pid) return 3; } +df::goal_type Units::getGoalType(df::unit *unit, size_t goalIndex) +{ + CHECK_NULL_POINTER(unit); + + df::goal_type goal = df::goal_type::STAY_ALIVE; + if (unit->status.current_soul + && unit->status.current_soul->personality.dreams.size() > goalIndex) + { + goal = unit->status.current_soul->personality.dreams[goalIndex]->type; + } + return goal; +} + +std::string Units::getGoalName(df::unit *unit, size_t goalIndex) +{ + CHECK_NULL_POINTER(unit); + + df::goal_type goal = getGoalType(unit, goalIndex); + bool achieved_goal = isGoalAchieved(unit, goalIndex); + + std::string goal_name = achieved_goal ? ENUM_ATTR(goal_type, achieved_short_name, goal) : ENUM_ATTR(goal_type, short_name, goal); + if (goal == df::goal_type::START_A_FAMILY) { + std::string parent = ENUM_KEY_STR(histfig_relationship_type, histfig_relationship_type::Parent); + size_t start_pos = goal_name.find(parent); + if (start_pos != std::string::npos) { + df::histfig_relationship_type parent_type = isFemale(unit) ? histfig_relationship_type::Mother : histfig_relationship_type::Father; + goal_name.replace(start_pos, parent.length(), ENUM_KEY_STR(histfig_relationship_type, parent_type)); + } + } + return goal_name; +} + +bool Units::isGoalAchieved(df::unit *unit, size_t goalIndex) +{ + CHECK_NULL_POINTER(unit); + + return unit->status.current_soul + && unit->status.current_soul->personality.dreams.size() > goalIndex + && unit->status.current_soul->personality.dreams[goalIndex]->flags.whole != 0; +} + std::string Units::getSquadName(df::unit *unit) { CHECK_NULL_POINTER(unit); diff --git a/library/xml b/library/xml index 9fca46ccc..036b662a1 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 9fca46ccca28e0948014b9d56a096ad7343473f1 +Subproject commit 036b662a1bbc96b4911f3cbe74dfa1243b6459bc diff --git a/package/linux/dfhack b/package/linux/dfhack index b63c3a253..6b542f405 100755 --- a/package/linux/dfhack +++ b/package/linux/dfhack @@ -146,6 +146,11 @@ case "$1" in exec setarch "$setarch_arch" -R env LD_PRELOAD="$PRELOAD_LIB" ./libs/Dwarf_Fortress "$@" # script does not resume ;; + --sc | --sizecheck) + PRELOAD_LIB="${PRELOAD_LIB:+$PRELOAD_LIB:}./hack/libsizecheck.so" + MALLOC_PERTURB_=45 setarch "$setarch_arch" -R env LD_PRELOAD="$PRELOAD_LIB" ./libs/Dwarf_Fortress "$@" + ret=$? + ;; *) setarch "$setarch_arch" -R env LD_PRELOAD="$PRELOAD_LIB" ./libs/Dwarf_Fortress "$@" ret=$? diff --git a/plugins/labormanager/joblabormapper.cpp b/plugins/labormanager/joblabormapper.cpp index 7b4e77553..6a97aa75f 100644 --- a/plugins/labormanager/joblabormapper.cpp +++ b/plugins/labormanager/joblabormapper.cpp @@ -313,9 +313,23 @@ public: return workshop_build_labor[ws->type]; } break; + case df::building_type::Furnace: + { + df::building_furnacest* frn = (df::building_furnacest*) bld; + if (frn->design && !frn->design->flags.bits.designed) + return df::unit_labor::ARCHITECT; + if (frn->type == df::furnace_type::Custom) + { + df::building_def* def = df::building_def::find(frn->custom_type); + return def->build_labors[0]; + } + else + // cast to building_actual should be safe here because at this point the building has been designed + return construction_build_labor((df::building_actual*)bld); + } + break; case df::building_type::Construction: return df::unit_labor::BUILD_CONSTRUCTION; - case df::building_type::Furnace: case df::building_type::TradeDepot: case df::building_type::Bridge: case df::building_type::ArcheryTarget: @@ -418,15 +432,26 @@ public: return workshop_build_labor[ws->type]; } break; + case df::building_type::Furnace: + { + df::building_furnacest* frn = (df::building_furnacest*) bld; + if (frn->type == df::furnace_type::Custom) + { + df::building_def* def = df::building_def::find(frn->custom_type); + return def->build_labors[0]; + } + else + // can't destroy a building if doesn't actually exist + return construction_build_labor((df::building_actual*)bld); + } + break; case df::building_type::Construction: return df::unit_labor::REMOVE_CONSTRUCTION; - case df::building_type::Furnace: case df::building_type::TradeDepot: case df::building_type::Wagon: case df::building_type::Bridge: case df::building_type::ScrewPump: case df::building_type::ArcheryTarget: - case df::building_type::RoadPaved: case df::building_type::Shop: case df::building_type::Support: case df::building_type::WaterWheel: @@ -437,6 +462,8 @@ public: return construction_build_labor(b); } break; + case df::building_type::RoadPaved: + return df::unit_labor::BUILD_ROAD; case df::building_type::FarmPlot: return df::unit_labor::PLANT; case df::building_type::Trap: diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index a013df335..b333afae5 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -32,6 +32,8 @@ #include "df/caste_raw.h" #include "df/historical_entity.h" #include "df/entity_raw.h" +#include "df/goal_type.h" +#include "df/unit_personality.h" #include "uicommon.h" #include "listcolumn.h" @@ -287,6 +289,9 @@ struct UnitInfo string name; string transname; string profession; + string goal; + df::pronoun_type goal_gender; + bool achieved_goal; int8_t color; int active_index; string squad_effective_name; @@ -310,7 +315,8 @@ struct UnitInfo enum detail_cols { DETAIL_MODE_PROFESSION, DETAIL_MODE_SQUAD, - DETAIL_MODE_JOB + DETAIL_MODE_JOB, + DETAIL_MODE_GOAL }; enum altsort_mode { ALTSORT_NAME, @@ -348,6 +354,21 @@ bool sortByProfession (const UnitInfo *d1, const UnitInfo *d2) return (d1->profession < d2->profession); } +bool sortByGoal (const UnitInfo *d1, const UnitInfo *d2) +{ + if (!d1->unit->status.current_soul) + return !descending; + if (!d2->unit->status.current_soul) + return descending; + + df::goal_type goal1 = Units::getGoalType(d1->unit); + df::goal_type goal2 = Units::getGoalType(d2->unit); + if (descending) + return (goal1 > goal2); + else + return (goal1 < goal2); +} + bool sortBySquad (const UnitInfo *d1, const UnitInfo *d2) { bool gt = false; @@ -583,6 +604,8 @@ namespace unit_ops { } string get_profname(UnitInfo *u) { return Units::getProfessionName(u->unit); } + string get_goalname(UnitInfo *u) + { return Units::getGoalName(u->unit); } string get_real_profname(UnitInfo *u) { string tmp = u->unit->custom_profession; @@ -1237,6 +1260,14 @@ void viewscreen_unitlaborsst::refreshNames() cur->name = Translation::TranslateName(Units::getVisibleName(unit), false); cur->transname = Translation::TranslateName(Units::getVisibleName(unit), true); cur->profession = Units::getProfessionName(unit); + cur->goal = Units::getGoalName(unit); + df::goal_type goal = Units::getGoalType(unit); + if (goal == df::goal_type::START_A_FAMILY) { + cur->goal_gender = unit->sex; + } else { + cur->goal_gender = df::pronoun_type::it; + } + cur->achieved_goal = Units::isGoalAchieved(unit); if (unit->job.current_job == NULL) { df::activity_event *event = Units::getMainSocialEvent(unit); @@ -1298,8 +1329,10 @@ void viewscreen_unitlaborsst::calcSize() detail_cmp = units[i]->squad_info.size(); } else if (detail_mode == DETAIL_MODE_JOB) { detail_cmp = units[i]->job_desc.size(); - } else { + } else if (detail_mode == DETAIL_MODE_PROFESSION) { detail_cmp = units[i]->profession.size(); + } else { + detail_cmp = units[i]->goal.size(); } if (size_t(col_maxwidth[DISP_COLUMN_DETAIL]) < detail_cmp) col_maxwidth[DISP_COLUMN_DETAIL] = detail_cmp; @@ -1736,8 +1769,10 @@ void viewscreen_unitlaborsst::feed(set *events) std::stable_sort(units.begin(), units.end(), sortBySquad); } else if (detail_mode == DETAIL_MODE_JOB) { std::stable_sort(units.begin(), units.end(), sortByJob); - } else { + } else if (detail_mode == DETAIL_MODE_PROFESSION) { std::stable_sort(units.begin(), units.end(), sortByProfession); + } else { + std::stable_sort(units.begin(), units.end(), sortByGoal); } break; case ALTSORT_STRESS: @@ -1777,10 +1812,13 @@ void viewscreen_unitlaborsst::feed(set *events) if (detail_mode == DETAIL_MODE_SQUAD) { detail_mode = DETAIL_MODE_JOB; } else if (detail_mode == DETAIL_MODE_JOB) { - detail_mode = DETAIL_MODE_PROFESSION; - } else { + detail_mode = DETAIL_MODE_GOAL; + } else if (detail_mode == DETAIL_MODE_PROFESSION) { detail_mode = DETAIL_MODE_SQUAD; + } else { + detail_mode = DETAIL_MODE_PROFESSION; } + calcSize(); } if (events->count(interface_key::CUSTOM_SHIFT_X)) @@ -1888,8 +1926,10 @@ void viewscreen_unitlaborsst::render() detail_str = "Squad"; } else if (detail_mode == DETAIL_MODE_JOB) { detail_str = "Job"; - } else { + } else if (detail_mode == DETAIL_MODE_PROFESSION) { detail_str = "Profession"; + } else { + detail_str = "Goal"; } Screen::paintString(Screen::Pen(' ', 7, 0), col_offsets[DISP_COLUMN_DETAIL], 2, detail_str); @@ -1978,9 +2018,32 @@ void viewscreen_unitlaborsst::render() } else { fg = COLOR_LIGHTCYAN; } - } else { + } else if (detail_mode == DETAIL_MODE_PROFESSION) { fg = cur->color; detail_str = cur->profession; + } else { + if (cur->goal_gender == df::pronoun_type::it) { + if (cur->achieved_goal) { + fg = COLOR_LIGHTGREEN; + } else { + fg = COLOR_BROWN; + } + } else if (cur->goal_gender == df::pronoun_type::she) { + if (cur->achieved_goal) { + fg = COLOR_LIGHTRED; + } + else { + fg = COLOR_MAGENTA; + } + } else { + if (cur->achieved_goal) { + fg = COLOR_LIGHTCYAN; + } + else { + fg = COLOR_BLUE; + } + } + detail_str = cur->goal; } detail_str.resize(col_widths[DISP_COLUMN_DETAIL]); Screen::paintString(Screen::Pen(' ', fg, bg), col_offsets[DISP_COLUMN_DETAIL], 4 + row, detail_str); @@ -2140,8 +2203,10 @@ void viewscreen_unitlaborsst::render() OutputString(15, x, y, "Squad"); } else if (detail_mode == DETAIL_MODE_JOB) { OutputString(15, x, y, "Job"); - } else { + } else if (detail_mode == DETAIL_MODE_PROFESSION) { OutputString(15, x, y, "Profession"); + } else { + OutputString(15, x, y, "Goal"); } break; case ALTSORT_STRESS: diff --git a/scripts b/scripts index 823d47c4d..568c586ce 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 823d47c4d181ac5b754dce5d605f3e7f242aed26 +Subproject commit 568c586ce2474af89c64a4cc7c876edb35931d43