From 89ed9950c7eb6ec4e01307ac06d8a1e3f7d5fc1c Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sun, 24 Apr 2022 22:32:41 -0700 Subject: [PATCH 01/32] Update Gui.h Added parseReportString, which parses a string using '&' as a control character (&r as newline, && as &) and cuts to a certain length w/o splitting words. Added autoDFAnnouncement, which takes a report_init and a string, and handles them like DF does. Added variants to log unprinted announcements and to build the report_init from arguments. Added pauseRecenter, which recenters on an xyz coord (item style, not unit) and optionally pauses, while respecting pause_zoom_no_interface_ms. Added variant that takes a pos. Added recenterViewscreen, which recenters on an xyz coord using a report zoom style (item, unit, generic. revealInDwarfmodeMap calls "unit" style "center". Generic style ignores coords and just enforces valid view bounds.) Added variants that take pos or use current cursor coords. --- library/include/modules/Gui.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/library/include/modules/Gui.h b/library/include/modules/Gui.h index 455032fea..098bb8dd3 100644 --- a/library/include/modules/Gui.h +++ b/library/include/modules/Gui.h @@ -36,6 +36,8 @@ distribution. #include "df/ui.h" #include "df/announcement_type.h" #include "df/announcement_flags.h" +#include "df/report_init.h" +#include "df/report_zoom_type.h" #include "df/unit_report_type.h" #include "modules/GuiHooks.h" @@ -113,6 +115,7 @@ namespace DFHack // Low-level API that gives full control over announcements and reports DFHACK_EXPORT void writeToGamelog(std::string message); + DFHACK_EXPORT bool parseReportString(std::vector &out, const std::string &str, size_t line_length = 73); DFHACK_EXPORT int makeAnnouncement(df::announcement_type type, df::announcement_flags mode, df::coord pos, std::string message, int color = 7, bool bright = true); DFHACK_EXPORT bool addCombatReport(df::unit *unit, df::unit_report_type slot, int report_index); DFHACK_EXPORT bool addCombatReportAuto(df::unit *unit, df::announcement_flags mode, int report_index); @@ -124,6 +127,11 @@ namespace DFHack // Show an announcement with effects determined by announcements.txt 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); + + // Process an announcement exactly like DF would, which might result in no announcement + DFHACK_EXPORT int autoDFAnnouncement(df::report_init r, std::string message); + DFHACK_EXPORT int autoDFAnnouncement(df::report_init r, std::string message, bool log_failures); + DFHACK_EXPORT int autoDFAnnouncement(df::announcement_type type, df::coord pos, std::string message, int color = 7, bool bright = true, df::unit *unit1 = NULL, df::unit *unit2 = NULL, bool sparring = false, bool log_failures = false); /* * Cursor and window coords @@ -131,6 +139,13 @@ namespace DFHack DFHACK_EXPORT df::coord getViewportPos(); DFHACK_EXPORT df::coord getCursorPos(); + // Recenter the viewscreen, based on DF code for announcements and scrolling + DFHACK_EXPORT void pauseRecenter(int32_t x, int32_t y, int32_t z, bool pause); + DFHACK_EXPORT inline void pauseRecenter(df::coord pos, bool pause) { return pauseRecenter(pos.x, pos.y, pos.z, pause); } + DFHACK_EXPORT void recenterViewscreen(int32_t x, int32_t y, int32_t z, df::report_zoom_type zoom = df::enums::report_zoom_type::Item); + DFHACK_EXPORT inline void recenterViewscreen(df::coord pos, df::report_zoom_type zoom = df::enums::report_zoom_type::Item) { recenterViewscreen(pos.x, pos.y, pos.z, zoom); }; + DFHACK_EXPORT inline void recenterViewscreen(df::report_zoom_type zoom = df::enums::report_zoom_type::Item) { recenterViewscreen(getCursorPos(), zoom); }; + static const int AREA_MAP_WIDTH = 23; static const int MENU_WIDTH = 30; From c7be54dac0f234b6337007f27f4b44ef1927bddd Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sun, 24 Apr 2022 22:45:26 -0700 Subject: [PATCH 02/32] Update Gui.cpp Add reverse engineered functions: parseReportString, autoDFAnnouncement, recenterViewscreen, and pauseRecenter. Add versions of autoDFAnnouncement that don't take a report_init struct and that log unprinted announcements. Add utility functions: recent_report, recent_report_any, delete_old_reports, and check_repeat_report. --- library/modules/Gui.cpp | 431 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 431 insertions(+) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 963b2ecd6..e6b865df0 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -44,6 +44,7 @@ using namespace DFHack; #include "modules/Job.h" #include "modules/Screen.h" #include "modules/Maps.h" +#include "modules/Units.h" #include "DataDefs.h" @@ -70,6 +71,7 @@ using namespace DFHack; #include "df/plant.h" #include "df/popup_message.h" #include "df/report.h" +#include "df/report_zoom_type.h" #include "df/route_stockpile_link.h" #include "df/stop_depart_condition.h" #include "df/ui_advmode.h" @@ -1360,6 +1362,137 @@ DFHACK_EXPORT void Gui::writeToGamelog(std::string message) fseed.close(); } +bool Gui::parseReportString(std::vector &out, const std::string &str, size_t line_length) +{ // out vector will contain strings cut to line_length, avoiding cutting up words + // Reverse-engineered from DF announcement code, fixes applied + + if (str.empty() || line_length == 0) + return false; + out.clear(); + + bool ignore_space = false; + string current_line = ""; + size_t iter = 0; + do + { + if (ignore_space) + { + if (str[iter] == ' ') + continue; + ignore_space = false; + } + + if (str[iter] == '&') // escape character + { + iter++; // ignore the '&' itself + if (iter >= str.length()) + break; + + if (str[iter] == 'r') // "&r" starts new line + { + if (!current_line.empty()) + { + out.push_back(string(current_line)); + current_line = ""; + } + out.push_back(" "); + continue; // don't add 'r' to current_line + } + else if (str[iter] != '&') + { // not "&&", don't add character to current_line + continue; + } + } + + current_line += str[iter]; + if (current_line.length() > line_length) + { + size_t i = current_line.length(); // start of current word + size_t j; // end of previous word + while (--i > 0 && current_line[i] != ' '); // find start of current word + + if (i == 0) + { // need to push at least one char + j = i = line_length; // last char ends up on next line + } + else + { + j = i; + while (j > 1 && current_line[j - 1] == ' ') + j--; // consume excess spaces at the split point + } + out.push_back(current_line.substr(0, j)); // push string before j + + if (current_line[i] == ' ') + i++; // don't keep this space + current_line.erase(0, i); // current_line now starts at last word or is empty + ignore_space = current_line.empty(); // ignore leading spaces on new line + } + } while (++iter < str.length()); + + if (!current_line.empty()) + out.push_back(current_line); + + return true; +} + +namespace +{ // Utility functions for reports + bool recent_report(df::unit *unit, df::unit_report_type slot) + { + if (unit && !unit->reports.log[slot].empty() && + *df::global::cur_year == unit->reports.last_year[slot] && + (*df::global::cur_year_tick - unit->reports.last_year_tick[slot]) <= 500) + { + return true; + } + return false; + } + + bool recent_report_any(df::unit *unit) + { + FOR_ENUM_ITEMS(unit_report_type, slot) + { + if (recent_report(unit, slot)) + return true; + } + return false; + } + + void delete_old_reports() + { + auto &reports = world->status.reports; + while (reports.size() > 3000) + { + if (reports[0] != NULL) + { + if (reports[0]->flags.bits.announcement) + erase_from_vector(world->status.announcements, &df::report::id, reports[0]->id); + delete reports[0]; + } + reports.erase(reports.begin()); + } + } + + int32_t check_repeat_report(vector &results) + { + if (*gamemode == game_mode::DWARF && !results.empty() && world->status.reports.size() >= results.size()) + { + auto &reports = world->status.reports; + size_t base = reports.size() - results.size(); // index where a repeat would start + size_t offset = 0; + while (reports[base + offset]->text == results[offset] && ++offset < results.size()); // match each report + + if (offset == results.size()) // all lines matched + { + reports[base]->duration = 100; + return ++(reports[base]->repeat_count); + } + } + return 0; + } +} + DFHACK_EXPORT int Gui::makeAnnouncement(df::announcement_type type, df::announcement_flags flags, df::coord pos, std::string message, int color, bool bright) { using df::global::world; @@ -1574,6 +1707,239 @@ void Gui::showAutoAnnouncement( addCombatReportAuto(unit2, flags, id); } +int Gui::autoDFAnnouncement(df::report_init r, string message) +{ // Reverse-engineered from DF announcement code + + if (!world->unk_26a9a8) // TODO: world->show_announcements + return 1; + + df::announcement_flags a_flags; + if (is_valid_enum_item(r.type)) + a_flags = df::global::d_init->announcements.flags[r.type]; + else + return 2; + + if (message.empty()) + { + Core::printerr("Empty announcement %u\n", r.type); // DF would print this to errorlog.txt + return 3; + } + + // Check if the announcement will actually be announced + if (*gamemode == game_mode::ADVENTURE) + { + if (r.pos.x != -30000 && + r.type != announcement_type::CREATURE_SOUND && + r.type != announcement_type::REGULAR_CONVERSATION && + r.type != announcement_type::CONFLICT_CONVERSATION && + r.type != announcement_type::MECHANISM_SOUND) + { // If not sound, make sure we can see pos + if ((world->units.active.empty() || (r.unit1 != world->units.active[0] && r.unit2 != world->units.active[0])) && + ((Maps::getTileDesignation(r.pos)->whole & 0x10) == 0x0)) // Adventure mode uses this bit to determine current visibility + { + return 4; + } + } + } + else + { // Dwarf mode (or arena?) + if ((r.unit1 != NULL || r.unit2 != NULL) && (r.unit1 == NULL || Units::isHidden(r.unit1)) && (r.unit2 == NULL || Units::isHidden(r.unit2))) + return 5; + + if (!a_flags.bits.D_DISPLAY) + { + if (a_flags.bits.UNIT_COMBAT_REPORT) + { + if (r.unit1 == NULL && r.unit2 == NULL) + return 6; + } + else + { + if (!a_flags.bits.UNIT_COMBAT_REPORT_ALL_ACTIVE) + return 7; + if (!recent_report_any(r.unit1) && !recent_report_any(r.unit2)) + return 8; + } + } + } + + if (a_flags.bits.PAUSE || a_flags.bits.RECENTER) + pauseRecenter((a_flags.bits.RECENTER ? r.pos : df::coord()), a_flags.bits.PAUSE); // Does nothing outside dwarf mode + + if (a_flags.bits.DO_MEGA && (*gamemode != game_mode::ADVENTURE || world->units.active.empty() || world->units.active[0]->counters.unconscious <= 0)) + showPopupAnnouncement(message, r.color, r.bright); + + vector results; + size_t line_length = (r.speaker_id == -1) ? (init->display.grid_x - 7) : (init->display.grid_x - 10); + parseReportString(results, message, line_length); + + if (results.empty()) + return 9; + + // Check for repeat report + int32_t repeat_count = check_repeat_report(results); + if (repeat_count > 0) + { + if (a_flags.bits.D_DISPLAY) + { + world->status.display_timer = r.display_timer; + Gui::writeToGamelog("x" + (repeat_count + 1)); + } + return 0; + } + + bool success = false; // only print to gamelog if report was used + size_t new_report_index = world->status.reports.size(); + for (size_t i = 0; i < results.size(); i++) + { // Generate report entries for each line + auto new_report = new df::report(); + new_report->type = r.type; + new_report->text = results[i]; + new_report->color = r.color; + new_report->bright = r.bright; + new_report->flags.whole = 0x0; + new_report->zoom_type = r.zoom_type; + new_report->pos = r.pos; + new_report->zoom_type2 = r.zoom_type2; + new_report->pos2 = r.pos2; + new_report->id = world->status.next_report_id++; + new_report->year = *df::global::cur_year; + new_report->time = *df::global::cur_year_tick; + new_report->unk_v40_1 = r.unk_v40_1; + new_report->unk_v40_2 = r.unk_v40_2; + new_report->speaker_id = r.speaker_id; + world->status.reports.push_back(new_report); + + if (i > 0) + new_report->flags.bits.continuation = true; + + if (*gamemode == game_mode::ADVENTURE && !world->units.active.empty() && world->units.active[0]->counters.unconscious > 0) + new_report->flags.bits.unconscious = true; + + if ((*gamemode == game_mode::ADVENTURE && a_flags.bits.A_DISPLAY) || (*gamemode == game_mode::DWARF && a_flags.bits.D_DISPLAY)) + { + insert_into_vector(world->status.announcements, &df::report::id, new_report); + new_report->flags.bits.announcement = true; + world->status.display_timer = r.display_timer; + success = true; + } + } + + if (*gamemode == game_mode::DWARF) + { + if (a_flags.bits.UNIT_COMBAT_REPORT) + { + if (r.unit1 != NULL) + { + if (r.flags.bits.sparring) // TODO: flags.sparring is inverted + success |= addCombatReport(r.unit1, unit_report_type::Combat, new_report_index); + else if (r.unit1->job.current_job != NULL && r.unit1->job.current_job->job_type == job_type::Hunt) + success |= addCombatReport(r.unit1, unit_report_type::Hunting, new_report_index); + else + success |= addCombatReport(r.unit1, unit_report_type::Sparring, new_report_index); + } + + if (r.unit2 != NULL) + { + if (r.flags.bits.sparring) // TODO: flags.sparring is inverted + success |= addCombatReport(r.unit2, unit_report_type::Combat, new_report_index); + else if (r.unit2->job.current_job != NULL && r.unit2->job.current_job->job_type == job_type::Hunt) + success |= addCombatReport(r.unit2, unit_report_type::Hunting, new_report_index); + else + success |= addCombatReport(r.unit2, unit_report_type::Sparring, new_report_index); + } + } + + if (a_flags.bits.UNIT_COMBAT_REPORT_ALL_ACTIVE) + { + FOR_ENUM_ITEMS(unit_report_type, slot) + { + if (recent_report(r.unit1, slot)) + success |= addCombatReport(r.unit1, slot, new_report_index); + if (recent_report(r.unit2, slot)) + success |= addCombatReport(r.unit2, slot, new_report_index); + } + } + } + + delete_old_reports(); + + if (/*debug_gamelog &&*/ success) + Gui::writeToGamelog(message); + else if (success) + return 10; + else + return 11; + + return 0; +} + +int Gui::autoDFAnnouncement(df::report_init r, string message, bool log_failures) +{ // Prints info about failed announcements to DFHack console if log_failures is true + int rv = autoDFAnnouncement(r, message); + + if (log_failures) + { + switch (rv) + { + case 0: + break; // success + case 1: + Core::print("Skipped an announcement because world->show_announcements is false:\n%s\n", message.c_str()); + break; + case 2: + Core::printerr("Invalid announcement type!\n"); + break; + case 3: + break; // empty announcement, already handled + case 4: + Core::print("An adventure announcement occured, but nobody heard:\n%s\n", message.c_str()); + break; + case 5: + Core::print("An announcement occured, but nobody heard:\n%s\n", message.c_str()); + break; + case 6: + Core::print("Skipped a UNIT_COMBAT_REPORT because it has no units:\n%s\n", message.c_str()); + break; + case 7: + Core::print("Skipped an announcement not enabled for this game mode:\n%s\n", message.c_str()); + break; + case 8: + Core::print("Skipped an announcement because there's no active report:\n%s\n", message.c_str()); + break; + case 9: + Core::print("Skipped an announcement because it was empty after parsing:\n%s\n", message.c_str()); + break; + case 10: + Core::print("Report added but skipped printing to gamelog.txt because debug_gamelog is false.\n"); + break; + case 11: + Core::print("Report added but didn't qualify to be displayed anywhere:\n%s\n", message.c_str()); + break; + default: + Core::printerr("autoDFAnnouncement: Unexpected return value!\n"); + } + } + return rv; +} + +int Gui::autoDFAnnouncement(df::announcement_type type, df::coord pos, std::string message, int color, bool bright, df::unit *unit1, df::unit *unit2, bool sparring, bool log_failures) +{ + auto r = df::report_init(); + r.type = type; + r.color = color; + r.bright = bright; + r.pos = pos; + r.unit1 = unit1; + r.unit2 = unit2; + r.flags.bits.sparring = !sparring; // TODO: inverted + + if (Maps::isValidTilePos(pos)) + r.zoom_type = report_zoom_type::Unit; + + return autoDFAnnouncement(r, message, log_failures); +} + df::viewscreen *Gui::getCurViewscreen(bool skip_dismissed) { if (!gview) @@ -1624,6 +1990,71 @@ df::coord Gui::getCursorPos() return df::coord(cursor->x, cursor->y, cursor->z); } +void Gui::recenterViewscreen(int32_t x, int32_t y, int32_t z, df::report_zoom_type zoom) +{ + // Reverse-engineered from DF announcement code, also used when scrolling + + auto dims = getDwarfmodeViewDims(); + int32_t w = dims.map_x2 - dims.map_x1 + 1; + int32_t h = dims.map_y2 - dims.map_y1 + 1; + int32_t new_win_x, new_win_y, new_win_z; + getViewCoords(new_win_x, new_win_y, new_win_z); + + if (zoom != report_zoom_type::Generic && x != -30000) + { + if (zoom == report_zoom_type::Unit) + { + new_win_x = x - w / 2; + new_win_y = y - h / 2; + } + else // report_zoom_type::Item + { + if (new_win_x > (x - 5)) + new_win_x -= (new_win_x - (x - 5) - 1) / 10 * 10 + 10; + if (new_win_y > (y - 5)) + new_win_y -= (new_win_y - (y - 5) - 1) / 10 * 10 + 10; + if (new_win_x < (x + 5 - w)) + new_win_x += ((x + 5 - w) - new_win_x - 1) / 10 * 10 + 10; + if (new_win_y < (y + 5 - h)) + new_win_y += ((y + 5 - h) - new_win_y - 1) / 10 * 10 + 10; + } + + if (new_win_z != z) + ui_sidebar_menus->minimap.need_scan = true; + new_win_z = z; + } + + *df::global::window_x = clip_range(new_win_x, 0, (world->map.x_count - w)); + *df::global::window_y = clip_range(new_win_y, 0, (world->map.y_count - h)); + *df::global::window_z = clip_range(new_win_z, 0, (world->map.z_count - 1)); + + return; +} + +void Gui::pauseRecenter(int32_t x, int32_t y, int32_t z, bool pause) +{ + // Reverse-engineered from DF announcement code + + if (*gamemode != game_mode::DWARF) + return; + + resetDwarfmodeView(pause); + if (x != -30000) + { + recenterViewscreen(x, y, z, report_zoom_type::Item); + ui_sidebar_menus->minimap.need_render = true; + ui_sidebar_menus->minimap.need_scan = true; + } + + if (init->input.pause_zoom_no_interface_ms > 0) + { + gview->shutdown_interface_tickcount = Core::getInstance().p->getTickCount(); + gview->shutdown_interface_for_ms = init->input.pause_zoom_no_interface_ms; + } + + return; +} + Gui::DwarfmodeDims getDwarfmodeViewDims_default() { Gui::DwarfmodeDims dims; From 861a0ee85ecf75cb1d35a0ee0dd2e3b2e528bc51 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sun, 24 Apr 2022 22:52:31 -0700 Subject: [PATCH 03/32] Update LuaApi.cpp Wrappers for autoDFAnnouncement, pauseRecenter, and recenterViewscreen. --- library/LuaApi.cpp | 122 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 101b645fd..fcdd96cbf 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -101,6 +101,8 @@ distribution. #include "df/specific_ref.h" #include "df/specific_ref_type.h" #include "df/vermin.h" +#include "df/report_init.h" +#include "df/report_zoom_type.h" #include #include @@ -1505,7 +1507,127 @@ static const LuaWrapper::FunctionReg dfhack_gui_module[] = { { NULL, NULL } }; +static int gui_autoDFAnnouncement(lua_State *state) +{ + int rv; + df::report_init *r = Lua::GetDFObject(state, 1); + + if (r) + { + std::string message = luaL_checkstring(state, 2); + + if (lua_gettop(state) >= 3) + rv = Gui::autoDFAnnouncement(*r, message, lua_toboolean(state, 3)); + else + rv = Gui::autoDFAnnouncement(*r, message); + } + else + { + df::coord pos; + int color; + bool bright, sparring, log_failures; + df::unit *unit1, *unit2; + + auto type = (df::announcement_type)lua_tointeger(state, 1); + Lua::CheckDFAssign(state, &pos, 2); + std::string message = luaL_checkstring(state, 3); + + switch (lua_gettop(state)) + { + default: + case 9: + log_failures = lua_toboolean(state, 9); + case 8: + sparring = lua_toboolean(state, 8); + case 7: + unit2 = Lua::CheckDFObject(state, 7); + case 6: + unit1 = Lua::CheckDFObject(state, 6); + case 5: + bright = lua_toboolean(state, 5); + case 4: + color = lua_tointeger(state, 4); + case 3: + break; + } + + switch (lua_gettop(state)) + { // Use the defaults in Gui.h + default: + case 9: + rv = Gui::autoDFAnnouncement(type, pos, message, color, bright, unit1, unit2, sparring, log_failures); + break; + case 8: + rv = Gui::autoDFAnnouncement(type, pos, message, color, bright, unit1, unit2, sparring); + break; + case 7: + rv = Gui::autoDFAnnouncement(type, pos, message, color, bright, unit1, unit2); + break; + case 6: + rv = Gui::autoDFAnnouncement(type, pos, message, color, bright, unit1); + break; + case 5: + rv = Gui::autoDFAnnouncement(type, pos, message, color, bright); + break; + case 4: + rv = Gui::autoDFAnnouncement(type, pos, message, color); + break; + case 3: + rv = Gui::autoDFAnnouncement(type, pos, message); + } + } + + lua_pushinteger(state, rv); + return 1; +} + +static int gui_pauseRecenter(lua_State *state) +{ + if (lua_gettop(state) == 2) + { + df::coord p; + Lua::CheckDFAssign(state, &p, 1); + Gui::pauseRecenter(p, lua_toboolean(state, 2)); + } + else + Gui::pauseRecenter(CheckCoordXYZ(state, 1, false), lua_toboolean(state, 4)); + + return 1; +} + +static int gui_recenterViewscreen(lua_State *state) +{ + df::coord p; + switch (lua_gettop(state)) + { + default: + case 4: + Gui::recenterViewscreen(CheckCoordXYZ(state, 1, false), (df::report_zoom_type)lua_tointeger(state, 4)); + break; + case 3: + Gui::recenterViewscreen(CheckCoordXYZ(state, 1, false)); + break; + case 2: + Lua::CheckDFAssign(state, &p, 1); + Gui::recenterViewscreen(p, (df::report_zoom_type)lua_tointeger(state, 2)); + break; + case 1: + if (lua_type(state, 1) == LUA_TNUMBER) + Gui::recenterViewscreen((df::report_zoom_type)lua_tointeger(state, 1)); + else + Gui::recenterViewscreen(CheckCoordXYZ(state, 1, true)); + break; + case 0: + Gui::recenterViewscreen(); + } + + return 1; +} + static const luaL_Reg dfhack_gui_funcs[] = { + { "autoDFAnnouncement", gui_autoDFAnnouncement }, + { "pauseRecenter", gui_pauseRecenter }, + { "recenterViewscreen", gui_recenterViewscreen }, { "getDwarfmodeViewDims", gui_getDwarfmodeViewDims }, { NULL, NULL } }; From c66448015eeb28e40d4b8c76474c6642d0a39b85 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Mon, 25 Apr 2022 00:02:37 -0700 Subject: [PATCH 04/32] Update Lua API.rst Document autoDFAnnouncement, pauseRecenter, and recenterViewscreen. --- docs/Lua API.rst | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index a0764df99..946af7afe 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1004,6 +1004,22 @@ Fortress mode Resets the fortress mode sidebar menus and cursors to their default state. If ``pause`` is true, also pauses the game. +* ``dfhack.gui.pauseRecenter(pos[,pause]) +* ``dfhack.gui.pauseRecenter(x,y,z[,pause]) + + Same as ``resetDwarfmodeView``, but also recenter if ``x`` isn't ``-30000``, + and respects RECENTER_INTERFACE_SHUTDOWN_MS in DF's init.txt. + +* ``dfhack.gui.recenterViewscreen(pos[,zoom]) +* ``dfhack.gui.recenterViewscreen(x,y,z[,zoom]) +* ``dfhack.gui.recenterViewscreen([zoom]) + + Recenter the view on a position using a specific zoom type. If no position is + given, recenter on ``df.global.cursor``. Zoom types are ``df.report_zoom_type`` + (0 = Generic, 1 = Item, 2 = Unit), where Generic skips recentering and + enforces valid view bounds (the same as x = -30000.) + Default zoom type is Item. + * ``dfhack.gui.revealInDwarfmodeMap(pos)`` Centers the view on the given position, which can be a ``df.coord`` instance @@ -1073,6 +1089,14 @@ 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.autoDFAnnouncement(report,text[,log_failures]) +* ``dfhack.gui.autoDFAnnouncement(type,pos,text,color[,is_bright,unit1,unit2,is_sparring,log_failures]) + + Takes a ``df.report_init`` and a string and processes them just like DF does. + Sometimes this means the announcement won't occur. Set ``log_failures`` to ``true`` to + log the reason why to the dfhack console (e.g., unrevealed map or wrong gamemode.) + Can also be built from parameters instead of a ``report_init``. + Other ~~~~~ From 709adda9682b9e91f9b0a36ad6bc691639ef6519 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Mon, 25 Apr 2022 00:06:53 -0700 Subject: [PATCH 05/32] Update Lua API.rst Fix formatting --- docs/Lua API.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 946af7afe..d4aa284a9 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1004,15 +1004,15 @@ Fortress mode Resets the fortress mode sidebar menus and cursors to their default state. If ``pause`` is true, also pauses the game. -* ``dfhack.gui.pauseRecenter(pos[,pause]) -* ``dfhack.gui.pauseRecenter(x,y,z[,pause]) +* ``dfhack.gui.pauseRecenter(pos[,pause])`` +* ``dfhack.gui.pauseRecenter(x,y,z[,pause])`` Same as ``resetDwarfmodeView``, but also recenter if ``x`` isn't ``-30000``, and respects RECENTER_INTERFACE_SHUTDOWN_MS in DF's init.txt. -* ``dfhack.gui.recenterViewscreen(pos[,zoom]) -* ``dfhack.gui.recenterViewscreen(x,y,z[,zoom]) -* ``dfhack.gui.recenterViewscreen([zoom]) +* ``dfhack.gui.recenterViewscreen(pos[,zoom])`` +* ``dfhack.gui.recenterViewscreen(x,y,z[,zoom])`` +* ``dfhack.gui.recenterViewscreen([zoom])`` Recenter the view on a position using a specific zoom type. If no position is given, recenter on ``df.global.cursor``. Zoom types are ``df.report_zoom_type`` @@ -1089,8 +1089,8 @@ 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.autoDFAnnouncement(report,text[,log_failures]) -* ``dfhack.gui.autoDFAnnouncement(type,pos,text,color[,is_bright,unit1,unit2,is_sparring,log_failures]) +* ``dfhack.gui.autoDFAnnouncement(report,text[,log_failures])`` +* ``dfhack.gui.autoDFAnnouncement(type,pos,text,color[,is_bright,unit1,unit2,is_sparring,log_failures])`` Takes a ``df.report_init`` and a string and processes them just like DF does. Sometimes this means the announcement won't occur. Set ``log_failures`` to ``true`` to From ad9a08f1e73ab5afd5a7703460a0fa7065f37ac9 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Mon, 25 Apr 2022 00:10:26 -0700 Subject: [PATCH 06/32] Update Lua API.rst Fix formatting (again) --- docs/Lua API.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index d4aa284a9..33d6b0a44 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1005,14 +1005,14 @@ Fortress mode ``pause`` is true, also pauses the game. * ``dfhack.gui.pauseRecenter(pos[,pause])`` -* ``dfhack.gui.pauseRecenter(x,y,z[,pause])`` + ``dfhack.gui.pauseRecenter(x,y,z[,pause])`` Same as ``resetDwarfmodeView``, but also recenter if ``x`` isn't ``-30000``, and respects RECENTER_INTERFACE_SHUTDOWN_MS in DF's init.txt. * ``dfhack.gui.recenterViewscreen(pos[,zoom])`` -* ``dfhack.gui.recenterViewscreen(x,y,z[,zoom])`` -* ``dfhack.gui.recenterViewscreen([zoom])`` + ``dfhack.gui.recenterViewscreen(x,y,z[,zoom])`` + ``dfhack.gui.recenterViewscreen([zoom])`` Recenter the view on a position using a specific zoom type. If no position is given, recenter on ``df.global.cursor``. Zoom types are ``df.report_zoom_type`` @@ -1090,7 +1090,7 @@ Announcements operations accordingly. The units are used to call ``addCombatReportAuto``. * ``dfhack.gui.autoDFAnnouncement(report,text[,log_failures])`` -* ``dfhack.gui.autoDFAnnouncement(type,pos,text,color[,is_bright,unit1,unit2,is_sparring,log_failures])`` + ``dfhack.gui.autoDFAnnouncement(type,pos,text,color[,is_bright,unit1,unit2,is_sparring,log_failures])`` Takes a ``df.report_init`` and a string and processes them just like DF does. Sometimes this means the announcement won't occur. Set ``log_failures`` to ``true`` to From a47bf1533ed5307b023d75fc2343becc4ff665e8 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Mon, 25 Apr 2022 00:17:31 -0700 Subject: [PATCH 07/32] Update changelog.txt --- docs/changelog.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index 1a3bf64b4..cae8a7871 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -62,6 +62,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Documentation - Add more examples to the plugin skeleton files so they are more informative for a newbie - Lua API.rst added: ``isHidden(unit)``, ``isFortControlled(unit)``, ``getOuterContainerRef(unit)``, ``getOuterContainerRef(item)`` +- Lua API.rst added: ``autoDFAnnouncement``, ``pauseRecenter``, ``recenterViewscreen`` - Update download link and installation instructions for Visual C++ 2015 build tools on Windows - Updated information regarding obtaining a compatible Windows build environment - `confirm`: Correct the command name in the plugin help text @@ -70,11 +71,13 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## API - Added functions reverse-engineered from ambushing unit code: ``Units::isHidden``, ``Units::isFortControlled``, ``Units::getOuterContainerRef``, ``Items::getOuterContainerRef`` +- Added functions reverse-engineered from announcement code: ``Gui::parseReportString``, ``Gui::autoDFAnnouncement``, ``Gui::pauseRecenter``, ``Gui::recenterViewscreen`` ## Lua - ``widgets.FilteredList`` now allows all punctuation to be typed into the filter and can match search keys that start with punctuation. - ``widgets.ListBox``: minimum height of dialog is now calculated correctly when there are no items in the list (e.g. when a filter doesn't match anything) - Lua wrappers for functions reverse-engineered from ambushing unit code: ``isHidden(unit)``, ``isFortControlled(unit)``, ``getOuterContainerRef(unit)``, ``getOuterContainerRef(item)`` +- Lua wrappers for functions reverse-engineered from announcement code: ``autoDFAnnouncement``, ``pauseRecenter``, ``recenterViewscreen`` - ``dwarfmode.MenuOverlay``: if ``sidebar_mode`` attribute is set, automatically manage entering a specific sidebar mode on show and restoring the previous sidebar mode on dismiss - ``dwarfmode.enterSidebarMode()``: passing ``df.ui_sidebar_mode.DesignateMine`` now always results in you entering ``DesignateMine`` mode and not ``DesignateChopTrees``, even when you looking at the surface where the default designation mode is ``DesignateChopTrees`` - New string class function: ``string:escape_pattern()`` escapes regex special characters within a string From b56d9520e9ac34855a1dea1b4196ff7e07bb3404 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Mon, 25 Apr 2022 00:39:05 -0700 Subject: [PATCH 08/32] Fix trailing whitespace --- library/LuaApi.cpp | 2 +- library/modules/Gui.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index fcdd96cbf..16bef7fc7 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1507,7 +1507,7 @@ static const LuaWrapper::FunctionReg dfhack_gui_module[] = { { NULL, NULL } }; -static int gui_autoDFAnnouncement(lua_State *state) +static int gui_autoDFAnnouncement(lua_State *state) { int rv; df::report_init *r = Lua::GetDFObject(state, 1); diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index e6b865df0..ceea6251d 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -1991,7 +1991,7 @@ df::coord Gui::getCursorPos() } void Gui::recenterViewscreen(int32_t x, int32_t y, int32_t z, df::report_zoom_type zoom) -{ +{ // Reverse-engineered from DF announcement code, also used when scrolling auto dims = getDwarfmodeViewDims(); @@ -2032,7 +2032,7 @@ void Gui::recenterViewscreen(int32_t x, int32_t y, int32_t z, df::report_zoom_ty } void Gui::pauseRecenter(int32_t x, int32_t y, int32_t z, bool pause) -{ +{ // Reverse-engineered from DF announcement code if (*gamemode != game_mode::DWARF) From c89baa5e338c16ba3ea2cc4539e93d5b0d2db742 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Thu, 28 Apr 2022 22:40:36 -0700 Subject: [PATCH 09/32] Update Lua API.rst --- docs/Lua API.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 33d6b0a44..bad0481bb 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1008,7 +1008,7 @@ Fortress mode ``dfhack.gui.pauseRecenter(x,y,z[,pause])`` Same as ``resetDwarfmodeView``, but also recenter if ``x`` isn't ``-30000``, - and respects RECENTER_INTERFACE_SHUTDOWN_MS in DF's init.txt. + and respects RECENTER_INTERFACE_SHUTDOWN_MS (the delay before input is recognized when a recenter occurs) in DF's init.txt. * ``dfhack.gui.recenterViewscreen(pos[,zoom])`` ``dfhack.gui.recenterViewscreen(x,y,z[,zoom])`` @@ -1016,8 +1016,8 @@ Fortress mode Recenter the view on a position using a specific zoom type. If no position is given, recenter on ``df.global.cursor``. Zoom types are ``df.report_zoom_type`` - (0 = Generic, 1 = Item, 2 = Unit), where Generic skips recentering and - enforces valid view bounds (the same as x = -30000.) + (0 = Generic, 1 = Item, 2 = Unit), where ``Generic`` skips recentering and + enforces valid view bounds (the same as x = -30000,) ``Item`` brings the position onscreen, and ``Unit`` centers the screen on the position. Default zoom type is Item. * ``dfhack.gui.revealInDwarfmodeMap(pos)`` @@ -1090,12 +1090,12 @@ Announcements operations accordingly. The units are used to call ``addCombatReportAuto``. * ``dfhack.gui.autoDFAnnouncement(report,text[,log_failures])`` - ``dfhack.gui.autoDFAnnouncement(type,pos,text,color[,is_bright,unit1,unit2,is_sparring,log_failures])`` + ``dfhack.gui.autoDFAnnouncement(type,pos,text,color[,is_bright,unit1,unit2,not_sparring,log_failures])`` - Takes a ``df.report_init`` and a string and processes them just like DF does. + Takes a ``df.report_init`` (see: https://github.com/DFHack/df-structures/blob/master/df.announcements.xml#L451) and a string and processes them just like DF does. Sometimes this means the announcement won't occur. Set ``log_failures`` to ``true`` to log the reason why to the dfhack console (e.g., unrevealed map or wrong gamemode.) - Can also be built from parameters instead of a ``report_init``. + Can also be built from parameters instead of a ``report_init``. Setting ``not_sparring`` to ``false`` means it will be added to sparring logs (if applicable) rather than hunting or combat. Other ~~~~~ From f565de88e81a60ec1eb87621430cf9a8a899912b Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sun, 1 May 2022 22:53:53 -0700 Subject: [PATCH 10/32] Fix stuff (#4) * Update Lua API.rst * Update Gui.h * Update Gui.cpp * Update LuaApi.cpp --- docs/Lua API.rst | 24 ++++++++++++------------ library/LuaApi.cpp | 8 ++++---- library/include/modules/Gui.h | 2 +- library/modules/Gui.cpp | 8 ++++---- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index bad0481bb..11c1d41e3 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1007,18 +1007,17 @@ Fortress mode * ``dfhack.gui.pauseRecenter(pos[,pause])`` ``dfhack.gui.pauseRecenter(x,y,z[,pause])`` - Same as ``resetDwarfmodeView``, but also recenter if ``x`` isn't ``-30000``, - and respects RECENTER_INTERFACE_SHUTDOWN_MS (the delay before input is recognized when a recenter occurs) in DF's init.txt. + Same as ``resetDwarfmodeView``, but also recenter if ``x`` isn't ``-30000``, and respects + RECENTER_INTERFACE_SHUTDOWN_MS (the delay before input is recognized when a recenter occurs) in DF's init.txt. * ``dfhack.gui.recenterViewscreen(pos[,zoom])`` ``dfhack.gui.recenterViewscreen(x,y,z[,zoom])`` ``dfhack.gui.recenterViewscreen([zoom])`` - Recenter the view on a position using a specific zoom type. If no position is - given, recenter on ``df.global.cursor``. Zoom types are ``df.report_zoom_type`` - (0 = Generic, 1 = Item, 2 = Unit), where ``Generic`` skips recentering and - enforces valid view bounds (the same as x = -30000,) ``Item`` brings the position onscreen, and ``Unit`` centers the screen on the position. - Default zoom type is Item. + Recenter the view on a position using a specific zoom type. If no position is given, + recenter on ``df.global.cursor``. Zoom types are ``df.report_zoom_type`` (0 = Generic, 1 = Item, 2 = Unit), + where ``Generic`` skips recentering and enforces valid view bounds (the same as x = -30000,) ``Item`` brings + the position onscreen without centering, and ``Unit`` centers the screen on the position. Default zoom type is Item. * ``dfhack.gui.revealInDwarfmodeMap(pos)`` @@ -1090,12 +1089,13 @@ Announcements operations accordingly. The units are used to call ``addCombatReportAuto``. * ``dfhack.gui.autoDFAnnouncement(report,text[,log_failures])`` - ``dfhack.gui.autoDFAnnouncement(type,pos,text,color[,is_bright,unit1,unit2,not_sparring,log_failures])`` + ``dfhack.gui.autoDFAnnouncement(type,pos,text,color[,is_bright,unit1,unit2,is_sparring,log_failures])`` - Takes a ``df.report_init`` (see: https://github.com/DFHack/df-structures/blob/master/df.announcements.xml#L451) and a string and processes them just like DF does. - Sometimes this means the announcement won't occur. Set ``log_failures`` to ``true`` to - log the reason why to the dfhack console (e.g., unrevealed map or wrong gamemode.) - Can also be built from parameters instead of a ``report_init``. Setting ``not_sparring`` to ``false`` means it will be added to sparring logs (if applicable) rather than hunting or combat. + Takes a ``df.report_init`` (see: https://github.com/DFHack/df-structures/blob/master/df.announcements.xml#L451) + and a string and processes them just like DF does. Sometimes this means the announcement won't occur. + Set ``log_failures`` to ``true`` to log the reason why to the dfhack console (e.g., unrevealed map or wrong gamemode.) + Can also be built from parameters instead of a ``report_init``. Setting ``is_sparring`` to ``true`` means the report + will be added to sparring logs (if applicable) rather than hunting or combat. Other ~~~~~ diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 16bef7fc7..d9ebd7f84 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1525,7 +1525,7 @@ static int gui_autoDFAnnouncement(lua_State *state) { df::coord pos; int color; - bool bright, sparring, log_failures; + bool bright, is_sparring, log_failures; df::unit *unit1, *unit2; auto type = (df::announcement_type)lua_tointeger(state, 1); @@ -1538,7 +1538,7 @@ static int gui_autoDFAnnouncement(lua_State *state) case 9: log_failures = lua_toboolean(state, 9); case 8: - sparring = lua_toboolean(state, 8); + is_sparring = lua_toboolean(state, 8); case 7: unit2 = Lua::CheckDFObject(state, 7); case 6: @@ -1555,10 +1555,10 @@ static int gui_autoDFAnnouncement(lua_State *state) { // Use the defaults in Gui.h default: case 9: - rv = Gui::autoDFAnnouncement(type, pos, message, color, bright, unit1, unit2, sparring, log_failures); + rv = Gui::autoDFAnnouncement(type, pos, message, color, bright, unit1, unit2, is_sparring, log_failures); break; case 8: - rv = Gui::autoDFAnnouncement(type, pos, message, color, bright, unit1, unit2, sparring); + rv = Gui::autoDFAnnouncement(type, pos, message, color, bright, unit1, unit2, is_sparring); break; case 7: rv = Gui::autoDFAnnouncement(type, pos, message, color, bright, unit1, unit2); diff --git a/library/include/modules/Gui.h b/library/include/modules/Gui.h index 098bb8dd3..07672aaec 100644 --- a/library/include/modules/Gui.h +++ b/library/include/modules/Gui.h @@ -131,7 +131,7 @@ namespace DFHack // Process an announcement exactly like DF would, which might result in no announcement DFHACK_EXPORT int autoDFAnnouncement(df::report_init r, std::string message); DFHACK_EXPORT int autoDFAnnouncement(df::report_init r, std::string message, bool log_failures); - DFHACK_EXPORT int autoDFAnnouncement(df::announcement_type type, df::coord pos, std::string message, int color = 7, bool bright = true, df::unit *unit1 = NULL, df::unit *unit2 = NULL, bool sparring = false, bool log_failures = false); + DFHACK_EXPORT int autoDFAnnouncement(df::announcement_type type, df::coord pos, std::string message, int color = 7, bool bright = true, df::unit *unit1 = NULL, df::unit *unit2 = NULL, bool is_sparring = false, bool log_failures = false); /* * Cursor and window coords diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index ceea6251d..746a13b6a 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -1831,7 +1831,7 @@ int Gui::autoDFAnnouncement(df::report_init r, string message) { if (r.unit1 != NULL) { - if (r.flags.bits.sparring) // TODO: flags.sparring is inverted + if (r.flags.bits.hostile_combat) success |= addCombatReport(r.unit1, unit_report_type::Combat, new_report_index); else if (r.unit1->job.current_job != NULL && r.unit1->job.current_job->job_type == job_type::Hunt) success |= addCombatReport(r.unit1, unit_report_type::Hunting, new_report_index); @@ -1841,7 +1841,7 @@ int Gui::autoDFAnnouncement(df::report_init r, string message) if (r.unit2 != NULL) { - if (r.flags.bits.sparring) // TODO: flags.sparring is inverted + if (r.flags.bits.hostile_combat) success |= addCombatReport(r.unit2, unit_report_type::Combat, new_report_index); else if (r.unit2->job.current_job != NULL && r.unit2->job.current_job->job_type == job_type::Hunt) success |= addCombatReport(r.unit2, unit_report_type::Hunting, new_report_index); @@ -1923,7 +1923,7 @@ int Gui::autoDFAnnouncement(df::report_init r, string message, bool log_failures return rv; } -int Gui::autoDFAnnouncement(df::announcement_type type, df::coord pos, std::string message, int color, bool bright, df::unit *unit1, df::unit *unit2, bool sparring, bool log_failures) +int Gui::autoDFAnnouncement(df::announcement_type type, df::coord pos, std::string message, int color, bool bright, df::unit *unit1, df::unit *unit2, bool is_sparring, bool log_failures) { auto r = df::report_init(); r.type = type; @@ -1932,7 +1932,7 @@ int Gui::autoDFAnnouncement(df::announcement_type type, df::coord pos, std::stri r.pos = pos; r.unit1 = unit1; r.unit2 = unit2; - r.flags.bits.sparring = !sparring; // TODO: inverted + r.flags.bits.hostile_combat = !is_sparring; if (Maps::isValidTilePos(pos)) r.zoom_type = report_zoom_type::Unit; From 038b6f0d86636a69522a554919c945644804cb0d Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sun, 1 May 2022 23:07:52 -0700 Subject: [PATCH 11/32] Fix changelog conflicts --- docs/changelog.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index cae8a7871..6502f9573 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -38,7 +38,12 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Removed +## New Tweaks +- `tweak` partial-items: displays percentages on partially-consumed items such as hospital cloth + ## Fixes +- `cxxrandom`: fixed exception when calling ``bool_distribution`` +- `cxxrandom`: fixed id order for ShuffleSequence, but adds code to detect which parameter is which so each id is used correctly. 16000 limit before things get weird (previous was 16 bits) - `autofarm` removed restriction on only planting 'discovered' plants - `luasocket` (and others): return correct status code when closing socket connections @@ -58,8 +63,10 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `blueprint`: ``track`` phase renamed to ``carve``. carved fortifications and (optionally) engravings are now captured in blueprints - `tweak` stable-cursor: Keep the cursor stable even when the viewport moves a small amount - `cursecheck`: Added a new parameter, ``ids``, to print creature and race IDs of the cursed creature. +- Include recently-added tweaks in example dfhack.init file, clean up dreamfort onMapLoad.init file ## Documentation +- `cxxrandom`: added usage examples - Add more examples to the plugin skeleton files so they are more informative for a newbie - Lua API.rst added: ``isHidden(unit)``, ``isFortControlled(unit)``, ``getOuterContainerRef(unit)``, ``getOuterContainerRef(item)`` - Lua API.rst added: ``autoDFAnnouncement``, ``pauseRecenter``, ``recenterViewscreen`` @@ -68,6 +75,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `confirm`: Correct the command name in the plugin help text - ``Quickfort Blueprint Editing Guide``: added screenshots to the Dreamfort case study and overall clarified text - Document DFHack `lua-string` (``startswith``, ``endswith``, ``split``, ``trim``, ``wrap``, and ``escape_pattern``). +- Added a Rust client library to the `remote interface docs ` ## API - Added functions reverse-engineered from ambushing unit code: ``Units::isHidden``, ``Units::isFortControlled``, ``Units::getOuterContainerRef``, ``Items::getOuterContainerRef`` @@ -78,7 +86,9 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - ``widgets.ListBox``: minimum height of dialog is now calculated correctly when there are no items in the list (e.g. when a filter doesn't match anything) - Lua wrappers for functions reverse-engineered from ambushing unit code: ``isHidden(unit)``, ``isFortControlled(unit)``, ``getOuterContainerRef(unit)``, ``getOuterContainerRef(item)`` - Lua wrappers for functions reverse-engineered from announcement code: ``autoDFAnnouncement``, ``pauseRecenter``, ``recenterViewscreen`` +- Added `custom-raw-tokens` utility to Lua library for reading tokens added to raws by mods. - ``dwarfmode.MenuOverlay``: if ``sidebar_mode`` attribute is set, automatically manage entering a specific sidebar mode on show and restoring the previous sidebar mode on dismiss +- ``dwarfmode.MenuOverlay``: new class function ``renderMapOverlay`` to assist with drawing tiles over the visible map - ``dwarfmode.enterSidebarMode()``: passing ``df.ui_sidebar_mode.DesignateMine`` now always results in you entering ``DesignateMine`` mode and not ``DesignateChopTrees``, even when you looking at the surface where the default designation mode is ``DesignateChopTrees`` - New string class function: ``string:escape_pattern()`` escapes regex special characters within a string - ``widgets.Panel``: if ``autoarrange_subviews`` is set, ``Panel``\s will now automatically lay out widgets vertically according to their current height. This allows you to have widgets dynamically change height or become visible/hidden and you don't have to worry about recalculating frame layouts @@ -90,6 +100,8 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - ``widgets.ToggleHotkeyLabel``: new ``CycleHotkeyLabel`` subclass that toggles between ``On`` and ``Off`` states - ``safe_index`` now properly handles lua sparse tables that are indexed by numbers - ``widgets``: unset values in ``frame_inset``-table default to ``0`` +- ``dialogs``: ``show*`` functions now return a reference to the created dialog +- ``ensure_key``: new global function for retrieving or dynamically creating Lua table mappings # 0.47.05-r4 From 5be0fe2a44290aa1ac3a2a0a0f96334d18ef0cda Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Mon, 2 May 2022 23:52:47 -0700 Subject: [PATCH 12/32] Fix issues, update changelog and docs Initialize variables in LuaApi.cpp, solve changelog conflicts, hyperlink and escape char info in docs --- data/blueprints/library/dreamfort.csv | 66 ++++++++++++++----- docs/Lua API.rst | 5 +- docs/changelog.txt | 95 +++++++++++++-------------- library/LuaApi.cpp | 6 +- scripts | 2 +- 5 files changed, 103 insertions(+), 71 deletions(-) diff --git a/data/blueprints/library/dreamfort.csv b/data/blueprints/library/dreamfort.csv index c58ce9111..07aeb09a0 100644 --- a/data/blueprints/library/dreamfort.csv +++ b/data/blueprints/library/dreamfort.csv @@ -28,7 +28,7 @@ "" "Dreamfort works best at an embark site that is flat and has at least one soil layer. New players should avoid embarks with aquifers if they are not prepared to deal with them. Bring picks for mining, an axe for woodcutting, and an anvil for a forge. Bring a few blocks to speed up initial workshop construction as well. That's all you really need, but see the example embark profile in the online spreadsheets for a more complete setup." "" -"Other DFHack scripts and plugins also work very well with Dreamfort, such as autofarm, automelt, autonestbox, burial, prioritize, seedwatch, tailor, and, of course, buildingplan. An init file that configures all these plugins is distributed with DFHack as hack/examples/init/onMapLoad_dreamfort.init." +"Other DFHack commands also work very well with Dreamfort, such as autofarm, autonestbox, prioritize, seedwatch, tailor, and, of course, buildingplan. An init file that gets everything configured for you is distributed with DFHack as hack/examples/init/onMapLoad_dreamfort.init." Put that file in your Dwarf Fortress directory -- the same directory that has dfhack.init. "" "Also copy the files in hack/examples/orders/ to dfhack-config/orders/ and the files in hack/examples/professions/ to professions/. We'll be using these files later. See https://docs.dfhack.org/en/stable/docs/guides/examples-guide.html for more information, including suggestions on how many dwarves of each profession you are likely to need at each stage of fort maturity." @@ -46,7 +46,7 @@ interactively." "# The dreamfort.csv distributed with DFHack is generated with the following command: for fname in dreamfort*.xlsx; do xlsx2csv -a -p '' ""$fname""; done | sed 's/,*$//'" #notes label(checklist) command checklist -"Here is the recommended order for Dreamfort commands. You can copy/paste the command lines directly into the DFHack terminal, or, if you prefer, you can run the blueprints in the UI with gui/quickfort. See the level walkthroughs for context and details. Also remember to read the messages the blueprints print out after you run them so you don't miss any important manual steps." +"Here is the recommended order for Dreamfort commands. You can copy/paste the command lines directly into the DFHack terminal, or, if you prefer, you can run the blueprints in the UI with gui/quickfort. See the walkthroughs (the ""help"" blueprints) for context and details. Also remember to read the messages the blueprints print out after you run them so you don't miss any important manual steps." "" -- Preparation (before you embark!) -- Copy hack/examples/init/onMapLoad_dreamfort.init to your DF directory @@ -67,7 +67,7 @@ quickfort run library/dreamfort.csv -n /surface1,# Run when you find your center quickfort run library/dreamfort.csv -n /dig_all,"# Run when you find a suitable rock layer for the industry level. It designates digging for industry, services, guildhall, suites, and apartments all in one go. This list does not include the farming level, which we'll dig in the uppermost soil layer a bit later. Note that it is more efficient for your miners if you designate your digging before they dig the central stairs past that level since the stairs are dug at a low priority. This keeps your miners focused on one level at a time. If you need to designate your levels individually due to caverns interrupting the sequence or just because it is your preference, run the level-specific dig blueprints (i.e. /industry1, /services1, /guildhall1, /suites1, and 5 levels of /apartments1) instead of running /dig_all." "" -- Core fort (should finish at about the third migration wave) -- -quickfort run library/dreamfort.csv -n /surface2,# Run after initial trees are cleared. +quickfort run library/dreamfort.csv -n /surface2,"# Run after initial trees are cleared. If you are deconstructing the wagon now, wait until after the wagon is deconstructed so the jobs that depend on wagon contents (e.g. blocks) don't get canceled later." quickfort run library/dreamfort.csv -n /farming1,# Run when channels are dug and the additional designated trees are cleared. quickfort run library/dreamfort.csv -n /farming2,# Run when the farming level has been dug out. quickfort run library/dreamfort.csv -n /surface3,# Run right after /farming2. @@ -78,7 +78,7 @@ quickfort run library/dreamfort.csv -n /surface4,"# Run after the walls and floo "quickfort run,orders library/dreamfort.csv -n /services2",# Run when the services levels have been dug out. Feel free to remove the orders for the ropes if you already brought them with you. orders import basic,"# Run after the first migration wave, so you have dorfs to do all the basic tasks. Note that this is the ""orders"" plugin, not the ""quickfort orders"" command." "quickfort run,orders library/dreamfort.csv -n /surface5","# Run when all marked trees on the surface are chopped down and walls and floors have been constructed, including the roof section over the future barracks." -prioritize ConstructBuilding,# Run when you see the bridges ready to be built so the masons come and actually build them. +prioritize ConstructBuilding,# Run when you see the bridges ready to be built so the busy masons come and build them. "quickfort run,orders library/dreamfort.csv -n /surface6",# Run when at least the beehives and weapon rack are constructed and you have linked all levers to their respective bridges. "quickfort run,orders library/dreamfort.csv -n /surface7",# Run after the surface walls are completed and any marked trees are chopped down. "" @@ -94,6 +94,7 @@ orders import furnace,# Automated production of basic furnace-related items. Don "quickfort run,orders library/dreamfort.csv -n /apartments3",# Run when all beds have been constructed on the first apartments level. "quickfort run,orders library/dreamfort.csv -n /services3","# Run after the dining table and chair, weapon rack, and archery targets have been constructed. Also wait until after you complete /surface7, though, because surface defenses are more important than a grand dining hall." "quickfort run,orders library/dreamfort.csv -n /guildhall2",# Run when the guildhall level has been dug out. +"quickfort run,orders library/dreamfort.csv -n /guildhall3",# Optionally run after /guildhall2 to build default furnishings. "quickfort run,orders library/dreamfort.csv -n /farming4",# Run once you have a cache of potash. orders import military,# Automated production of military equipment. Turn on automelt in the meltables piles on the industry level to automatically upgrade all metal military equipment to masterwork quality. These orders are optional if you are not using a military. orders import smelting,# Automated production of all types of metal bars. @@ -104,7 +105,7 @@ orders import glassstock,# Maintains a small stock of all types of glass furnitu -- Repeat for each remaining apartments level as needed -- "quickfort run,orders library/dreamfort.csv -n /apartments2",# Run when the apartment level has been dug out. "quickfort run,orders library/dreamfort.csv -n /apartments3",# Run when all beds have been constructed. -burial -pets,# Run once the coffins are placed to set them to allow for burial. This is handled for you if you are using the provided onMapLoad_dreamfort.init file. +burial -pets,# Run once the coffins are placed to set them to allow for burial. See this checklist online at https://docs.google.com/spreadsheets/d/13PVZ2h3Mm3x_G1OXQvwKd7oIR2lK4A1Ahf6Om1kFigw/edit#gid=1459509569 #notes label(setup_help) @@ -113,14 +114,14 @@ Makes common initial adjustments to in-game settings. "The /setup blueprint is intended to be run once at the start of the game, before anything else is changed. Players are welcome to make any further customizations after this blueprint is run. Please be sure to run the /setup blueprint before making any other changes, though, so your settings are not overwritten!" "" The following settings are changed: -"- The manager, chief medical dwarf, broker, and bookkeeper noble roles are assigned to the first suggested dwarf. This is likely to be the expedition leader, but the game could suggest others if they have relevant skills. Bookkeeping is also set to the highest precision." +"- The manager, chief medical dwarf, broker, and bookkeeper noble roles are assigned to the first suggested dwarf. This is likely to be the expedition leader, but the game could suggest others if they have relevant skills. Bookkeeping is set to the highest precision." "" - Standing orders are set to: - only farmers harvest - gather refuse from outside (incl. vermin) - - no autoloom (we'll be managing cloth production with automated orders) + - no autoloom (so the hospital always has thread -- we'll be managing cloth production with automated orders) "" -"- A burrow named ""Inside"" is created (it's up to the player to define the area). It is intended for use in getting your civilians to safety during sieges. An alert named ""Siege"" is also created and associated with the ""Inside"" burrow." +"- A burrow named ""Inside"" is created, but it's up to the player to define the area as the fort expands. It is intended for use in getting your civilians to safety during sieges. A military alert named ""Siege"" is also created and associated with the ""Inside"" burrow." "" - Military uniforms get the following modifications: - all default uniforms set to replace clothing @@ -144,7 +145,7 @@ starthotkeys: H{sethotkey fkey={F2} name=Farming}{sethotkey fkey={F3} name=Indus "" "#config label(setup) message(Please set the zoom targets of the hotkeys (the 'H' menu) according to where you actually end up digging the levels. As you build your fort, expand the ""Inside"" burrow to include new civilian-safe areas. -Optionally, add a leather cloak to your military uniforms to enhance the protection of the uniforms. +Optionally, add a leather cloak to your military uniforms to enhance their protection rating. Nothing in Dreamfort depends on these settings staying as they are. Feel free to change any setting to your personal preference.) assign nobles, set standing orders, create burrows, make adjustments to military uniforms, and set hotkey names" {startnobles}{startorders}{startburrows}{startmilitary}{starthotkeys} "#meta label(dig_all) start(central stairs on industry level) dig industry, services, guildhall, suites, and apartments levels. does not include farming." @@ -532,7 +533,7 @@ Feel free to assign an unimportant animal to the pasture in the main entranceway -"#place label(surface_place_start) start(19; 19) hidden() message(if you haven't already, now is a good time to deconstruct the wagon) starting stockpiles" +#place label(surface_place_start) start(19; 19) hidden() starting stockpiles @@ -974,7 +975,7 @@ You might also want to set the ""trade goods quantum"" stockpile to Auto Trade i ,,,`,,`,nocontainers,crafts,,,,,,,`,,,,"{givename name=""inner main gate""}",,,,`,,,,,,,,,`,,` ,,,`,,`,"{givename name=""trade goods""}",,,,,,,,,,,,,,,,,,,,,,,,,`,,` ,,,`,,`,{forbidmasterworkfinishedgoods}{forbidartifactfinishedgoods},,,,,,,,"{givename name=""trade depo gate""}",,,,,,,,"{givename name=""barracks gate""}",,,,,,,,,`,,` -,,,`,,`,,"{quantumstopfromnorth name=""Trade Goods Dumper""}",,,,,,,,,,,,,,,,,"{quantumstop name=""Prisoner quantum"" move={Up 5} move_back={Down 5} route_enable={prisoner_route_enable}}{givename name=""prisoner dumper""}",,,,,,,`,,` +,,,`,,`,,"{quantumstopfromnorth name=""Trade Goods quantum""}",,,,,,,,,,,,,,,,,"{quantumstop name=""Prisoner quantum"" move={Up 5} move_back={Down 5} route_enable={prisoner_route_enable}}{givename name=""prisoner dumper""}",,,,,,,`,,` ,,,`,,`,,"{quantum name=""trade goods quantum""}",,,,,,,`,,,,,,,,`,,"{quantum name=""prisoner quantum"" quantum_enable={enableanimals}}",,,,,,,`,,` ,,,`,,`,`,`,`,`,`,`,`,`,`,,,,,,,,`,`,`,`,`,`,`,`,`,`,,` ,,,`,,"{givename name=""left outer gate""}",,,,,,,,,"{givename name=""left inner gate""}",,,,,,,,"{givename name=""right inner gate""}",,,,,,,,,"{givename name=""right outer gate""}",,` @@ -2554,14 +2555,14 @@ query_jail/services_query_jail Screenshot: https://drive.google.com/file/d/17jHiCKeZm6FSS-CI4V0r0GJZh09nzcO_ "" Features: -"- Big rooms, optionally pre-furnished. Double-thick walls to ensure engravings add value to the ""correct"" side. Fill with furniture and assign as needed." +"- Big rooms, optionally pre-furnished. Double-thick walls to ensure engravings add value to the ""correct"" side. Declare locations as needed." "" Guildhall Walkthrough: 1) Dig out the rooms with /guildhall1. "" -"2) Once the area is dug out, add doors and a few statues with /guildhall2. Run ""quickfort orders"" for /guildhall2." +"2) Once the area is dug out, pre-create the zones and add doors and a few statues with /guildhall2. Run ""quickfort orders"" for /guildhall2." "" -"3) Furnish individual rooms manually, or alternately get default furnishings for a variety of room types by running /guildhall3. Declare appropriate locations as you need guildhalls, libraries, and temples. If you need more rooms, you can dig another /guildhall1 in an unused z-level." +"3) Furnish individual rooms manually, or get default furnishings for a variety of room types by running /guildhall3. Declare appropriate locations from the pre-created zones as you need guildhalls, libraries, and temples. If you need more rooms, you can dig another /guildhall1 in an unused z-level." "#dig label(guildhall1) start(15; 15; central stairs) message(Once the area is dug out, continue with /guildhall2.)" @@ -2592,7 +2593,10 @@ Guildhall Walkthrough: ,,d,d,d,d,d,d,d,,,d,d,d,d,d,d,d,,,d,d,d,d,d,d,d -"#build label(guildhall2) start(15; 15; central stairs) message(Remember to enqueue manager orders for this blueprint. +#meta label(guildhall2) +doors/guildhall_doors +zones/guildhall_zones +"#build label(guildhall_doors) start(15; 15; central stairs) hidden() message(Remember to enqueue manager orders for this blueprint. Smooth/engrave tiles, furnish rooms, and declare locations as required.) build doors" @@ -2623,6 +2627,36 @@ Smooth/engrave tiles, furnish rooms, and declare locations as required.) build d ,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +#zone label(guildhall_zones) start(15; 15; central stairs) hidden() designate zones + +,m(9x9),,,,,,,,,m(9x9),,,,,,,,,m(9x9) +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,~,~,`,`,`,`,`,`,`,~,~,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,,,,,,~,,,,,,~,,~,,,,,,~ +,m(9x9),,,,,,~,,,,,,`,~,`,,,,m(9x9),,~ +,,`,`,`,`,`,`,`,,,,,~,,~,,,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,~,`,~,`,,,,`,~,`,~,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,~,,`,,`,,`,,~,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,~,`,~,`,,,,`,~,`,~,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,,,~,,~,,,,,`,`,`,`,`,`,` +,,,,,,,~,,,,,,`,~,`,,,,,,~ +,m(9x9),,,,,,~,,,m(9x9),,,~,,~,,,,m(9x9),,~ +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,~,~,`,`,`,`,`,`,`,~,~,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` + + "#build label(guildhall3) start(15; 15; central stairs) message(Remember to enqueue manager orders for this blueprint.) furnish 4 guildhalls, 3 temples, and a library" @@ -2676,7 +2710,7 @@ Apartments Walkthrough: "" "3) Once the beds are built, configure the rooms and build the remaining furniture with /apartments3. Run ""quickfort orders"" for /apartments3." "" -"4) Once the coffins are all in place, run ""burial -pets"" to set them all to accept burials. This is handled for you if you're using the onMapLoad_dreamfort.init file included with DFHack." +"4) Once the coffins are all in place, run ""burial -pets"" to set them all to accept burials." "#dig label(suites1) start(18; 18; central ramp) message(Once the area is dug out, run /suites2) noble suites" ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d diff --git a/docs/Lua API.rst b/docs/Lua API.rst index caaab0040..c13897e07 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1091,11 +1091,12 @@ Announcements * ``dfhack.gui.autoDFAnnouncement(report,text[,log_failures])`` ``dfhack.gui.autoDFAnnouncement(type,pos,text,color[,is_bright,unit1,unit2,is_sparring,log_failures])`` - Takes a ``df.report_init`` (see: https://github.com/DFHack/df-structures/blob/master/df.announcements.xml#L451) + Takes a ``df.report_init`` (see: `structure definition `_) and a string and processes them just like DF does. Sometimes this means the announcement won't occur. Set ``log_failures`` to ``true`` to log the reason why to the dfhack console (e.g., unrevealed map or wrong gamemode.) Can also be built from parameters instead of a ``report_init``. Setting ``is_sparring`` to ``true`` means the report - will be added to sparring logs (if applicable) rather than hunting or combat. + will be added to sparring logs (if applicable) rather than hunting or combat. Text uses ``&`` as an escape character, with ``&r`` being a newline, + ``&&`` being just ``&``, and any other combination causing neither character to display. Other ~~~~~ diff --git a/docs/changelog.txt b/docs/changelog.txt index 6502f9573..6e60e38c9 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -34,74 +34,71 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: # Future ## New Plugins -- `spectate`: automates the following of dwarves more often than not based on job zlevel activity levels, sometimes randomly though. - -## Removed +- `spectate`: "spectator mode" -- automatically follows dwarves doing things in your fort ## New Tweaks -- `tweak` partial-items: displays percentages on partially-consumed items such as hospital cloth +- `tweak`: ``partial-items`` displays percentage remaining for partially-consumed items such as hospital cloth ## Fixes +- `autofarm`: removed restriction on only planting "discovered" plants - `cxxrandom`: fixed exception when calling ``bool_distribution`` -- `cxxrandom`: fixed id order for ShuffleSequence, but adds code to detect which parameter is which so each id is used correctly. 16000 limit before things get weird (previous was 16 bits) -- `autofarm` removed restriction on only planting 'discovered' plants -- `luasocket` (and others): return correct status code when closing socket connections +- `luasocket`: return correct status code when closing socket connections so clients can know when to retry ## Misc Improvements +- `autochop`: only designate the amount of trees required to reach ``max_logs`` +- `autochop`: preferably designate larger trees over smaller ones +- `blueprint`: ``track`` phase renamed to ``carve`` +- `blueprint`: carved fortifications and (optionally) engravings are now captured in generated blueprints +- `cursecheck`: new option, ``--ids`` prints creature and race IDs of the cursed creature +- `debug`: DFHack log messages now have configurable headers (e.g. timestamp, origin plugin name, etc.) via the ``debugfilter`` command of the `debug` plugin +- `debug`: script execution log messages (e.g. "Loading script: dfhack_extras.init" can now be controlled with the ``debugfilter`` command. To hide the messages, add this line to your ``dfhack.init`` file: ``debugfilter set Warning core script`` +- `dfhack-examples-guide`: add mugs to ``basic`` manager orders +- `dfhack-examples-guide`: ``onMapLoad_dreamfort.init`` remove "cheaty" commands and new tweaks that are now in the default ``dfhack.init-example`` file +- ``dfhack.init-example``: recently-added tweaks added to example ``dfhack.init`` file - `dig-now`: handle fortification carving - `EventManager`: add new event type ``JOB_STARTED``, triggered when a job first gains a worker -- `EventManager`: add new event type ``NEW_UNIT_ACTIVE``, triggered when a new unit appears on the active list -- `EventManager`: now each registered handler for an event can have its own frequency instead of all handlers using the lowest frequency of all handlers -- `stocks`: allow search terms to match the full item label, even when the label is truncated for length -- `dfhack-examples-guide`: add mugs to ``basic`` manager orders +- `EventManager`: add new event type ``UNIT_NEW_ACTIVE``, triggered when a new unit appears on the active list - `gui/create-item`: Added "(chain)" annotation text for armours with the [CHAIN_METAL_TEXT] flag set -- DFHack log messages now have configurable headers (e.g. timestamp, origin plugin name, etc.) via the ``debugfilter`` command of the `debug` plugin -- Script execution log messages (e.g. "Loading script: dfhack_extras.init" can now be controlled with the ``debugfilter`` command. To hide the messages, add this line to your ``dfhack.init`` file: ``debugfilter set Warning core script`` -- `manipulator`: Tweak colors to make the cursor easier to locate -- `autochop`: only designate the amount of trees required to reach ``max_logs`` -- `autochop`: preferably designate larger trees over smaller ones -- `blueprint`: ``track`` phase renamed to ``carve``. carved fortifications and (optionally) engravings are now captured in blueprints -- `tweak` stable-cursor: Keep the cursor stable even when the viewport moves a small amount -- `cursecheck`: Added a new parameter, ``ids``, to print creature and race IDs of the cursed creature. -- Include recently-added tweaks in example dfhack.init file, clean up dreamfort onMapLoad.init file +- `manipulator`: tweak colors to make the cursor easier to locate +- `stocks`: allow search terms to match the full item label, even when the label is truncated for length +- `tweak`: ``stable-cursor`` now keeps the cursor stable even when the viewport moves a small amount ## Documentation +- add more examples to the plugin example skeleton files so they are more informative for a newbie +- `confirm`: correct the command name in the plugin help text - `cxxrandom`: added usage examples -- Add more examples to the plugin skeleton files so they are more informative for a newbie -- Lua API.rst added: ``isHidden(unit)``, ``isFortControlled(unit)``, ``getOuterContainerRef(unit)``, ``getOuterContainerRef(item)`` -- Lua API.rst added: ``autoDFAnnouncement``, ``pauseRecenter``, ``recenterViewscreen`` -- Update download link and installation instructions for Visual C++ 2015 build tools on Windows -- Updated information regarding obtaining a compatible Windows build environment -- `confirm`: Correct the command name in the plugin help text -- ``Quickfort Blueprint Editing Guide``: added screenshots to the Dreamfort case study and overall clarified text -- Document DFHack `lua-string` (``startswith``, ``endswith``, ``split``, ``trim``, ``wrap``, and ``escape_pattern``). -- Added a Rust client library to the `remote interface docs ` +- ``Lua API.rst``: added ``isHidden(unit)``, ``isFortControlled(unit)``, ``getOuterContainerRef(unit)``, ``getOuterContainerRef(item)``, ``autoDFAnnouncement``, ``pauseRecenter``, ``recenterViewscreen`` +- `lua-string`: document DFHack string extensions (``startswith()``, ``endswith()``, ``split()``, ``trim()``, ``wrap()``, and ``escape_pattern()``) +- `quickfort-blueprint-guide`: added screenshots to the Dreamfort case study and overall clarified text +- `remote-client-libs`: add new Rust client library +- update download link and installation instructions for Visual C++ 2015 build tools on Windows +- update information regarding obtaining a compatible Windows build environment ## API -- Added functions reverse-engineered from ambushing unit code: ``Units::isHidden``, ``Units::isFortControlled``, ``Units::getOuterContainerRef``, ``Items::getOuterContainerRef`` -- Added functions reverse-engineered from announcement code: ``Gui::parseReportString``, ``Gui::autoDFAnnouncement``, ``Gui::pauseRecenter``, ``Gui::recenterViewscreen`` +- add functions reverse-engineered from ambushing unit code: ``Units::isHidden()``, ``Units::isFortControlled()``, ``Units::getOuterContainerRef()``, ``Items::getOuterContainerRef()`` +- add functions reverse-engineered from announcement code: ``Gui::parseReportString``, ``Gui::autoDFAnnouncement``, ``Gui::pauseRecenter``, ``Gui::recenterViewscreen`` ## Lua -- ``widgets.FilteredList`` now allows all punctuation to be typed into the filter and can match search keys that start with punctuation. -- ``widgets.ListBox``: minimum height of dialog is now calculated correctly when there are no items in the list (e.g. when a filter doesn't match anything) -- Lua wrappers for functions reverse-engineered from ambushing unit code: ``isHidden(unit)``, ``isFortControlled(unit)``, ``getOuterContainerRef(unit)``, ``getOuterContainerRef(item)`` -- Lua wrappers for functions reverse-engineered from announcement code: ``autoDFAnnouncement``, ``pauseRecenter``, ``recenterViewscreen`` -- Added `custom-raw-tokens` utility to Lua library for reading tokens added to raws by mods. -- ``dwarfmode.MenuOverlay``: if ``sidebar_mode`` attribute is set, automatically manage entering a specific sidebar mode on show and restoring the previous sidebar mode on dismiss -- ``dwarfmode.MenuOverlay``: new class function ``renderMapOverlay`` to assist with drawing tiles over the visible map -- ``dwarfmode.enterSidebarMode()``: passing ``df.ui_sidebar_mode.DesignateMine`` now always results in you entering ``DesignateMine`` mode and not ``DesignateChopTrees``, even when you looking at the surface where the default designation mode is ``DesignateChopTrees`` -- New string class function: ``string:escape_pattern()`` escapes regex special characters within a string -- ``widgets.Panel``: if ``autoarrange_subviews`` is set, ``Panel``\s will now automatically lay out widgets vertically according to their current height. This allows you to have widgets dynamically change height or become visible/hidden and you don't have to worry about recalculating frame layouts -- ``widgets.ResizingPanel``: new ``Panel`` subclass that automatically recalculates it's own frame height based on the size, position, and visibility of its subviews -- ``widgets.WrappedLabel``: new ``Label`` subclass that provides autowrapping of text -- ``widgets.TooltipLabel``: new ``WrappedLabel`` subclass that provides tooltip-like behavior -- ``widgets.HotkeyLabel``: new ``Label`` subclass that displays and reacts to hotkeys -- ``widgets.CycleHotkeyLabel``: new ``Label`` subclass that allows users to cycle through a list of options by pressing a hotkey -- ``widgets.ToggleHotkeyLabel``: new ``CycleHotkeyLabel`` subclass that toggles between ``On`` and ``Off`` states -- ``safe_index`` now properly handles lua sparse tables that are indexed by numbers -- ``widgets``: unset values in ``frame_inset``-table default to ``0`` +- `custom-raw-tokens`: library for accessing tokens added to raws by mods +- ``dfhack.gui``: Lua wrappers for functions reverse-engineered from announcement code: ``autoDFAnnouncement``, ``pauseRecenter``, ``recenterViewscreen`` +- ``dfhack.units``: Lua wrappers for functions reverse-engineered from ambushing unit code: ``isHidden(unit)``, ``isFortControlled(unit)``, ``getOuterContainerRef(unit)``, ``getOuterContainerRef(item)`` - ``dialogs``: ``show*`` functions now return a reference to the created dialog +- ``dwarfmode.enterSidebarMode()``: passing ``df.ui_sidebar_mode.DesignateMine`` now always results in you entering ``DesignateMine`` mode and not ``DesignateChopTrees``, even when you looking at the surface (where the default designation mode is ``DesignateChopTrees``) +- ``dwarfmode.MenuOverlay``: if ``sidebar_mode`` attribute is set, automatically manage entering a specific sidebar mode on show and restoring the previous sidebar mode on dismiss +- ``dwarfmode.MenuOverlay``: new class function ``renderMapOverlay`` to assist with painting tiles over the visible map - ``ensure_key``: new global function for retrieving or dynamically creating Lua table mappings +- ``safe_index``: now properly handles lua sparse tables that are indexed by numbers +- ``string``: new function ``escape_pattern()`` escapes regex special characters within a string +- ``widgets``: unset values in ``frame_inset`` table default to ``0`` +- ``widgets``: ``FilteredList`` class now allows all punctuation to be typed into the filter and can match search keys that start with punctuation +- ``widgets``: minimum height of ``ListBox`` dialog is now calculated correctly when there are no items in the list (e.g. when a filter doesn't match anything) +- ``widgets``: if ``autoarrange_subviews`` is set, ``Panel``\s will now automatically lay out widgets vertically according to their current height. This allows you to have widgets dynamically change height or become visible/hidden and you don't have to worry about recalculating frame layouts +- ``widgets``: new class ``ResizingPanel`` (subclass of ``Panel``) automatically recalculates its own frame height based on the size, position, and visibility of its subviews +- ``widgets``: new class ``HotkeyLabel`` (subclass of ``Label``) that displays and reacts to hotkeys +- ``widgets``: new class ``CycleHotkeyLabel`` (subclass of ``Label``) allows users to cycle through a list of options by pressing a hotkey +- ``widgets``: new class ``ToggleHotkeyLabel`` (subclass of ``CycleHotkeyLabel``) toggles between ``On`` and ``Off`` states +- ``widgets``: new class ``WrappedLabel`` (subclass of ``Label``) provides autowrapping of text +- ``widgets``: new class ``TooltipLabel`` (subclass of ``WrappedLabel``) provides tooltip-like behavior # 0.47.05-r4 diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index d9ebd7f84..6954e3996 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1524,9 +1524,9 @@ static int gui_autoDFAnnouncement(lua_State *state) else { df::coord pos; - int color; - bool bright, is_sparring, log_failures; - df::unit *unit1, *unit2; + int color = 0; //initialize these to prevent warning + bool bright = false, is_sparring = false, log_failures = false; + df::unit *unit1 = NULL, *unit2 = NULL; auto type = (df::announcement_type)lua_tointeger(state, 1); Lua::CheckDFAssign(state, &pos, 2); diff --git a/scripts b/scripts index 74f03c0e4..714ef87ae 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 74f03c0e4a5a7818b7a1cbc3576ce2c3d30d3696 +Subproject commit 714ef87aef713af94874c6441e7810b9cf9ef312 From 74499ad64ad02bc5d7ef5cc584445eadf260a5c6 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Tue, 3 May 2022 00:09:34 -0700 Subject: [PATCH 13/32] Use to_string on integer --- library/modules/Gui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 746a13b6a..ce43df71f 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -1783,7 +1783,7 @@ int Gui::autoDFAnnouncement(df::report_init r, string message) if (a_flags.bits.D_DISPLAY) { world->status.display_timer = r.display_timer; - Gui::writeToGamelog("x" + (repeat_count + 1)); + Gui::writeToGamelog("x" + to_string(repeat_count + 1)); } return 0; } From 018452af8284748320ec122796623176fa412498 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Thu, 5 May 2022 22:57:04 -0700 Subject: [PATCH 14/32] Update changelog.txt --- docs/changelog.txt | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 6e60e38c9..57fe888b6 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -33,6 +33,25 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: # Future +## New Plugins + +## New Tweaks + +## Fixes + +## Misc Improvements + +## Documentation +- ``Lua API.rst``: added ``autoDFAnnouncement``, ``pauseRecenter``, ``recenterViewscreen`` + +## API +- add functions reverse-engineered from announcement code: ``Gui::parseReportString``, ``Gui::autoDFAnnouncement``, ``Gui::pauseRecenter``, ``Gui::recenterViewscreen`` + +## Lua +- ``dfhack.gui``: Lua wrappers for functions reverse-engineered from announcement code: ``autoDFAnnouncement``, ``pauseRecenter``, ``recenterViewscreen`` + +# 0.47.05-r5 + ## New Plugins - `spectate`: "spectator mode" -- automatically follows dwarves doing things in your fort @@ -67,7 +86,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - add more examples to the plugin example skeleton files so they are more informative for a newbie - `confirm`: correct the command name in the plugin help text - `cxxrandom`: added usage examples -- ``Lua API.rst``: added ``isHidden(unit)``, ``isFortControlled(unit)``, ``getOuterContainerRef(unit)``, ``getOuterContainerRef(item)``, ``autoDFAnnouncement``, ``pauseRecenter``, ``recenterViewscreen`` +- ``Lua API.rst``: added ``isHidden(unit)``, ``isFortControlled(unit)``, ``getOuterContainerRef(unit)``, ``getOuterContainerRef(item)`` - `lua-string`: document DFHack string extensions (``startswith()``, ``endswith()``, ``split()``, ``trim()``, ``wrap()``, and ``escape_pattern()``) - `quickfort-blueprint-guide`: added screenshots to the Dreamfort case study and overall clarified text - `remote-client-libs`: add new Rust client library @@ -76,11 +95,9 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## API - add functions reverse-engineered from ambushing unit code: ``Units::isHidden()``, ``Units::isFortControlled()``, ``Units::getOuterContainerRef()``, ``Items::getOuterContainerRef()`` -- add functions reverse-engineered from announcement code: ``Gui::parseReportString``, ``Gui::autoDFAnnouncement``, ``Gui::pauseRecenter``, ``Gui::recenterViewscreen`` ## Lua - `custom-raw-tokens`: library for accessing tokens added to raws by mods -- ``dfhack.gui``: Lua wrappers for functions reverse-engineered from announcement code: ``autoDFAnnouncement``, ``pauseRecenter``, ``recenterViewscreen`` - ``dfhack.units``: Lua wrappers for functions reverse-engineered from ambushing unit code: ``isHidden(unit)``, ``isFortControlled(unit)``, ``getOuterContainerRef(unit)``, ``getOuterContainerRef(item)`` - ``dialogs``: ``show*`` functions now return a reference to the created dialog - ``dwarfmode.enterSidebarMode()``: passing ``df.ui_sidebar_mode.DesignateMine`` now always results in you entering ``DesignateMine`` mode and not ``DesignateChopTrees``, even when you looking at the surface (where the default designation mode is ``DesignateChopTrees``) From ce34ac8f33e56b89b90da76384825673a5f1dc42 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Mon, 16 May 2022 18:41:47 -0700 Subject: [PATCH 15/32] Use debugfilter; remove redundant changelog entries --- .github/workflows/build.yml | 4 +- .pre-commit-config.yaml | 1 + ci/script-docs.py | 4 +- ci/update-submodules.manifest | 2 + data/examples/orders/basic.json | 131 +++++++++++++++++++------------- depends/CMakeLists.txt | 8 +- depends/jsoncpp-sub | 2 +- depends/libexpat | 2 +- depends/libzip | 2 +- depends/lua/src/lapi.c | 2 +- depends/luacov | 2 +- depends/xlsxio | 2 +- docs/Authors.rst | 1 + docs/Lua API.rst | 11 ++- docs/Removed.rst | 8 ++ docs/changelog.txt | 3 +- library/CMakeLists.txt | 4 +- library/Debug.cpp | 2 +- library/LuaApi.cpp | 17 +---- library/MiscUtils.cpp | 58 ++++++++++---- library/include/Debug.h | 2 +- library/include/MiscUtils.h | 3 +- library/include/modules/Gui.h | 8 +- library/modules/Gui.cpp | 126 ++++++++++++++---------------- library/xml | 2 +- plugins/CMakeLists.txt | 6 +- plugins/autolabor.cpp | 2 +- plugins/command-prompt.cpp | 109 ++++++++++++++------------ plugins/isoworld | 2 +- scripts | 2 +- 30 files changed, 295 insertions(+), 233 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e8506ae9b..07febaa63 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -69,7 +69,7 @@ jobs: - name: Download DF run: | sh ci/download-df.sh - - name: Build DFHack + - name: Configure DFHack env: CC: gcc-${{ matrix.gcc }} CXX: g++-${{ matrix.gcc }} @@ -85,6 +85,8 @@ jobs: -DBUILD_STONESENSE:BOOL=${{ matrix.plugins == 'all' }} \ -DBUILD_SUPPORTED:BOOL=1 \ -DCMAKE_INSTALL_PREFIX="$DF_FOLDER" + - name: Build DFHack + run: | ninja -C build-ci install - name: Run tests id: run_tests diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 26d135ca2..336bda270 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,6 @@ ci: autofix_prs: false + autoupdate_schedule: monthly repos: # shared across repos: - repo: https://github.com/pre-commit/pre-commit-hooks diff --git a/ci/script-docs.py b/ci/script-docs.py index 71d7f37b2..7ef287f8a 100755 --- a/ci/script-docs.py +++ b/ci/script-docs.py @@ -81,11 +81,11 @@ def check_file(fname): def main(): """Check that all DFHack scripts include documentation""" err = 0 - exclude = set(['internal', 'test']) + exclude = {'.git', 'internal', 'test'} for root, dirs, files in os.walk(SCRIPT_PATH, topdown=True): dirs[:] = [d for d in dirs if d not in exclude] for f in files: - if f[-3:] in {'.rb', 'lua'}: + if f.split('.')[-1] in {'rb', 'lua'}: err += check_file(join(root, f)) return err diff --git a/ci/update-submodules.manifest b/ci/update-submodules.manifest index 4bd383018..e97cae6f3 100644 --- a/ci/update-submodules.manifest +++ b/ci/update-submodules.manifest @@ -1,7 +1,9 @@ library/xml master scripts master plugins/stonesense master +plugins/isoworld dfhack depends/libzip dfhack depends/libexpat dfhack depends/xlsxio dfhack depends/luacov dfhack +depends/jsoncpp-sub dfhack diff --git a/data/examples/orders/basic.json b/data/examples/orders/basic.json index fb4668e81..e8dee1680 100644 --- a/data/examples/orders/basic.json +++ b/data/examples/orders/basic.json @@ -1,12 +1,34 @@ [ { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", + "amount_left" : 150, + "amount_total" : 150, + "frequency" : "Monthly", "id" : 0, "is_active" : false, "is_validated" : false, "item_conditions" : + [ + { + "condition" : "LessThan", + "flags" : + [ + "unrotten" + ], + "item_type" : "FOOD", + "value" : 400 + } + ], + "job" : "PrepareMeal", + "meal_ingredients" : 2 + }, + { + "amount_left" : 10, + "amount_total" : 10, + "frequency" : "Daily", + "id" : 1, + "is_active" : false, + "is_validated" : false, + "item_conditions" : [ { "condition" : "AtLeast", @@ -25,7 +47,7 @@ "unrotten", "cookable" ], - "value" : 15 + "value" : 500 }, { "condition" : "AtMost", @@ -35,6 +57,15 @@ ], "item_type" : "FOOD", "value" : 3500 + }, + { + "condition" : "AtLeast", + "flags" : + [ + "unrotten" + ], + "item_type" : "FOOD", + "value" : 400 } ], "job" : "PrepareMeal", @@ -44,7 +75,7 @@ "amount_left" : 2, "amount_total" : 2, "frequency" : "Daily", - "id" : 1, + "id" : 2, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -81,7 +112,7 @@ "amount_left" : 2, "amount_total" : 2, "frequency" : "Daily", - "id" : 2, + "id" : 3, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -118,7 +149,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 3, + "id" : 4, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -139,7 +170,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 4, + "id" : 5, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -174,7 +205,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 5, + "id" : 6, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -206,7 +237,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 6, + "id" : 7, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -238,7 +269,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 7, + "id" : 8, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -260,7 +291,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 8, + "id" : 9, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -293,7 +324,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 9, + "id" : 10, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -324,7 +355,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 10, + "id" : 11, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -357,7 +388,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 11, + "id" : 12, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -397,7 +428,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 12, + "id" : 13, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -423,7 +454,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 13, + "id" : 14, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -458,7 +489,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 14, + "id" : 15, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -493,7 +524,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 15, + "id" : 16, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -528,7 +559,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 16, + "id" : 17, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -559,7 +590,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 17, + "id" : 18, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -589,7 +620,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 18, + "id" : 19, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -613,7 +644,6 @@ ], "item_subtype" : "ITEM_TOOL_LARGE_POT", "item_type" : "TOOL", - "material" : "INORGANIC", "value" : 25 } ], @@ -625,7 +655,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 19, + "id" : 20, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -649,7 +679,6 @@ ], "item_subtype" : "ITEM_TOOL_JUG", "item_type" : "TOOL", - "material" : "INORGANIC", "value" : 10 } ], @@ -661,7 +690,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 20, + "id" : 21, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -690,7 +719,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 21, + "id" : 22, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -718,7 +747,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 22, + "id" : 23, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -746,7 +775,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 23, + "id" : 24, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -776,7 +805,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 24, + "id" : 25, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -806,7 +835,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 25, + "id" : 26, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -835,7 +864,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 26, + "id" : 27, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -866,7 +895,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 27, + "id" : 28, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -902,7 +931,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 28, + "id" : 29, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -938,7 +967,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 29, + "id" : 30, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -974,7 +1003,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 30, + "id" : 31, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -993,7 +1022,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 31, + "id" : 32, "is_active" : false, "is_validated" : true, "item_conditions" : @@ -1019,7 +1048,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 32, + "id" : 33, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1041,7 +1070,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 33, + "id" : 34, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1067,7 +1096,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 34, + "id" : 35, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1093,7 +1122,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 35, + "id" : 36, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1119,7 +1148,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 36, + "id" : 37, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1152,7 +1181,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 37, + "id" : 38, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1191,7 +1220,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 38, + "id" : 39, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1225,7 +1254,7 @@ "amount_left" : 4, "amount_total" : 4, "frequency" : "Daily", - "id" : 39, + "id" : 40, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1248,7 +1277,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 40, + "id" : 41, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1272,7 +1301,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 41, + "id" : 42, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1306,7 +1335,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 42, + "id" : 43, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1335,7 +1364,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 43, + "id" : 44, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1364,7 +1393,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 44, + "id" : 45, "is_active" : false, "is_validated" : true, "item_conditions" : @@ -1395,7 +1424,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 45, + "id" : 46, "is_active" : false, "is_validated" : false, "item_conditions" : diff --git a/depends/CMakeLists.txt b/depends/CMakeLists.txt index 0fcb4ca3c..405a9555e 100644 --- a/depends/CMakeLists.txt +++ b/depends/CMakeLists.txt @@ -12,10 +12,11 @@ endif() add_subdirectory(tthread) option(JSONCPP_WITH_TESTS "Compile and (for jsoncpp_check) run JsonCpp test executables" OFF) option(JSONCPP_WITH_POST_BUILD_UNITTEST "Automatically run unit-tests as a post build step" OFF) +option(JSONCPP_BUILD_SHARED_LIBS "Build jsoncpp_lib as a shared library." OFF) +option(JSONCPP_BUILD_OBJECT_LIBS "Build jsoncpp_lib as a object library." OFF) +option(JSONCPP_WITH_CMAKE_PACKAGE "Generate and install cmake package files" OFF) + add_subdirectory(jsoncpp-sub EXCLUDE_FROM_ALL) -if(UNIX) - set_target_properties(jsoncpp_lib_static PROPERTIES COMPILE_FLAGS "-Wno-deprecated-declarations") -endif() # build clsocket static and only as a dependency. Setting those options here overrides its own default settings. option(CLSOCKET_SHARED "Build clsocket lib as shared." OFF) option(CLSOCKET_DEP_ONLY "Build for use inside other CMake projects as dependency." ON) @@ -37,6 +38,7 @@ if(UNIX) set_target_properties(expat PROPERTIES COMPILE_FLAGS "-Wno-maybe-uninitialized") endif() +set(CMAKE_REQUIRED_QUIET ON) set(LIBZIP_BUILD_DOC OFF CACHE BOOL "") set(LIBZIP_BUILD_EXAMPLES OFF CACHE BOOL "") set(LIBZIP_BUILD_REGRESS OFF CACHE BOOL "") diff --git a/depends/jsoncpp-sub b/depends/jsoncpp-sub index ddabf50f7..ba5eac541 160000 --- a/depends/jsoncpp-sub +++ b/depends/jsoncpp-sub @@ -1 +1 @@ -Subproject commit ddabf50f72cf369bf652a95c4d9fe31a1865a781 +Subproject commit ba5eac54136064af94ab4a923ac110d7534d4f83 diff --git a/depends/libexpat b/depends/libexpat index 3c0f2e86c..3e877cbb3 160000 --- a/depends/libexpat +++ b/depends/libexpat @@ -1 +1 @@ -Subproject commit 3c0f2e86ce4e7a3a3b30e765087d02a68bba7e6f +Subproject commit 3e877cbb3c9bc8f22946053e70490d2e5431f4d5 diff --git a/depends/libzip b/depends/libzip index da0d18ae5..081249cce 160000 --- a/depends/libzip +++ b/depends/libzip @@ -1 +1 @@ -Subproject commit da0d18ae59ef2699013316b703cdc93809414c93 +Subproject commit 081249cceb59adc857a72d67e60c32047680f787 diff --git a/depends/lua/src/lapi.c b/depends/lua/src/lapi.c index 711895b39..aa01148ab 100644 --- a/depends/lua/src/lapi.c +++ b/depends/lua/src/lapi.c @@ -395,7 +395,7 @@ LUA_API size_t lua_rawlen (lua_State *L, int idx) { case LUA_TSHRSTR: return tsvalue(o)->shrlen; case LUA_TLNGSTR: return tsvalue(o)->u.lnglen; case LUA_TUSERDATA: return uvalue(o)->len; - case LUA_TTABLE: return luaH_getn(hvalue(o)); + case LUA_TTABLE: return size_t(luaH_getn(hvalue(o))); default: return 0; } } diff --git a/depends/luacov b/depends/luacov index 87d6ae018..99d068278 160000 --- a/depends/luacov +++ b/depends/luacov @@ -1 +1 @@ -Subproject commit 87d6ae018cb8d288d854f632e9d8d959d75d7db4 +Subproject commit 99d06827848583232dd77afb34cd7ab589567086 diff --git a/depends/xlsxio b/depends/xlsxio index 4056226fe..ab8fd7f3e 160000 --- a/depends/xlsxio +++ b/depends/xlsxio @@ -1 +1 @@ -Subproject commit 4056226fe0df6bff4593ee2353cca07c2b7f327e +Subproject commit ab8fd7f3e9df457e8bc1b5cb31b78d57df0ac5a4 diff --git a/docs/Authors.rst b/docs/Authors.rst index 38fe19862..afb0a3f14 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -32,6 +32,7 @@ billw2012 billw2012 BrickViking brickviking brndd brndd burneddi Caldfir caldfir +Cameron Ewell Ozzatron Carter Bray Qartar Chris Dombroski cdombroski Chris Parsons chrismdp diff --git a/docs/Lua API.rst b/docs/Lua API.rst index c13897e07..7e1f45231 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1015,7 +1015,7 @@ Fortress mode ``dfhack.gui.recenterViewscreen([zoom])`` Recenter the view on a position using a specific zoom type. If no position is given, - recenter on ``df.global.cursor``. Zoom types are ``df.report_zoom_type`` (0 = Generic, 1 = Item, 2 = Unit), + recenter on ``df.global.cursor``. Zoom types are ``df.report_zoom_type`` (see: `enum definition `_), where ``Generic`` skips recentering and enforces valid view bounds (the same as x = -30000,) ``Item`` brings the position onscreen without centering, and ``Unit`` centers the screen on the position. Default zoom type is Item. @@ -1088,15 +1088,14 @@ 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.autoDFAnnouncement(report,text[,log_failures])`` - ``dfhack.gui.autoDFAnnouncement(type,pos,text,color[,is_bright,unit1,unit2,is_sparring,log_failures])`` +* ``dfhack.gui.autoDFAnnouncement(report,text)`` + ``dfhack.gui.autoDFAnnouncement(type,pos,text,color[,is_bright,unit1,unit2,is_sparring])`` Takes a ``df.report_init`` (see: `structure definition `_) and a string and processes them just like DF does. Sometimes this means the announcement won't occur. - Set ``log_failures`` to ``true`` to log the reason why to the dfhack console (e.g., unrevealed map or wrong gamemode.) Can also be built from parameters instead of a ``report_init``. Setting ``is_sparring`` to ``true`` means the report - will be added to sparring logs (if applicable) rather than hunting or combat. Text uses ``&`` as an escape character, with ``&r`` being a newline, - ``&&`` being just ``&``, and any other combination causing neither character to display. + will be added to sparring logs (if applicable) rather than hunting or combat. Text is parsed using ``&`` as an escape character, with ``&r`` + being a newline, ``&&`` being just ``&``, and any other combination causing neither character to display. Other ~~~~~ diff --git a/docs/Removed.rst b/docs/Removed.rst index 5d68b2133..54e83a6fc 100644 --- a/docs/Removed.rst +++ b/docs/Removed.rst @@ -10,6 +10,14 @@ work (e.g. links from the `changelog`). :local: :depth: 1 +.. _devel/unforbidall: + +devel/unforbidall +================= + +Replaced by the `unforbid` script. Run ``unforbid all --quiet`` to match the +behavior of the original ``devel/unforbidall`` script. + .. _digfort: digfort diff --git a/docs/changelog.txt b/docs/changelog.txt index 57fe888b6..dacf382fd 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -40,15 +40,14 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Fixes ## Misc Improvements +- `dfhack-examples-guide`: refine food preparation orders and fix conditions for making jugs and pots in the ``basic`` manager orders ## Documentation -- ``Lua API.rst``: added ``autoDFAnnouncement``, ``pauseRecenter``, ``recenterViewscreen`` ## API - add functions reverse-engineered from announcement code: ``Gui::parseReportString``, ``Gui::autoDFAnnouncement``, ``Gui::pauseRecenter``, ``Gui::recenterViewscreen`` ## Lua -- ``dfhack.gui``: Lua wrappers for functions reverse-engineered from announcement code: ``autoDFAnnouncement``, ``pauseRecenter``, ``recenterViewscreen`` # 0.47.05-r5 diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 4ec165308..9e7bf8590 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -415,10 +415,10 @@ if(APPLE) set_target_properties(dfhack PROPERTIES SOVERSION 1.0.0) endif() -target_link_libraries(dfhack protobuf-lite clsocket lua jsoncpp_lib_static dfhack-version ${PROJECT_LIBS}) +target_link_libraries(dfhack protobuf-lite clsocket lua jsoncpp_static dfhack-version ${PROJECT_LIBS}) set_target_properties(dfhack PROPERTIES INTERFACE_LINK_LIBRARIES "") -target_link_libraries(dfhack-client protobuf-lite clsocket jsoncpp_lib_static) +target_link_libraries(dfhack-client protobuf-lite clsocket jsoncpp_static) target_link_libraries(dfhack-run dfhack-client) if(APPLE) diff --git a/library/Debug.cpp b/library/Debug.cpp index 7ac981d30..9b13af168 100644 --- a/library/Debug.cpp +++ b/library/Debug.cpp @@ -198,7 +198,7 @@ DebugCategory::cstring_ref DebugCategory::plugin() const noexcept //! standards only provide runtime checks if an atomic type is lock free struct failIfEnumAtomicIsNotLockFree { failIfEnumAtomicIsNotLockFree() { - std::atomic test; + std::atomic test(DebugCategory::LINFO); if (test.is_lock_free()) return; std::cerr << __FILE__ << ':' << __LINE__ diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 6954e3996..5881e1b1f 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1509,23 +1509,19 @@ static const LuaWrapper::FunctionReg dfhack_gui_module[] = { static int gui_autoDFAnnouncement(lua_State *state) { - int rv; + bool rv; df::report_init *r = Lua::GetDFObject(state, 1); if (r) { std::string message = luaL_checkstring(state, 2); - - if (lua_gettop(state) >= 3) - rv = Gui::autoDFAnnouncement(*r, message, lua_toboolean(state, 3)); - else - rv = Gui::autoDFAnnouncement(*r, message); + rv = Gui::autoDFAnnouncement(*r, message); } else { df::coord pos; int color = 0; //initialize these to prevent warning - bool bright = false, is_sparring = false, log_failures = false; + bool bright = false, is_sparring = false; df::unit *unit1 = NULL, *unit2 = NULL; auto type = (df::announcement_type)lua_tointeger(state, 1); @@ -1535,8 +1531,6 @@ static int gui_autoDFAnnouncement(lua_State *state) switch (lua_gettop(state)) { default: - case 9: - log_failures = lua_toboolean(state, 9); case 8: is_sparring = lua_toboolean(state, 8); case 7: @@ -1554,9 +1548,6 @@ static int gui_autoDFAnnouncement(lua_State *state) switch (lua_gettop(state)) { // Use the defaults in Gui.h default: - case 9: - rv = Gui::autoDFAnnouncement(type, pos, message, color, bright, unit1, unit2, is_sparring, log_failures); - break; case 8: rv = Gui::autoDFAnnouncement(type, pos, message, color, bright, unit1, unit2, is_sparring); break; @@ -1577,7 +1568,7 @@ static int gui_autoDFAnnouncement(lua_State *state) } } - lua_pushinteger(state, rv); + lua_pushboolean(state, rv); return 1; } diff --git a/library/MiscUtils.cpp b/library/MiscUtils.cpp index 56af85afe..6ab63d5eb 100644 --- a/library/MiscUtils.cpp +++ b/library/MiscUtils.cpp @@ -168,32 +168,58 @@ std::string to_search_normalized(const std::string &str) return result; } -bool word_wrap(std::vector *out, const std::string &str, size_t line_length) + +bool word_wrap(std::vector *out, const std::string &str, + size_t line_length, bool collapse_whitespace) { - out->clear(); - std::istringstream input(str); - std::string out_line; - std::string word; - if (input >> word) + if (line_length == 0) + line_length = SIZE_MAX; + + std::string line; + size_t break_pos = 0; + + for (auto &c : str) { - out_line += word; - // size_t remaining = line_length - std::min(line_length, word.length()); - while (input >> word) + if (c == '\n') + { + out->push_back(line); + line.clear(); + break_pos = 0; + continue; + } + + if (isspace(c)) { - if (out_line.length() + word.length() + 1 <= line_length) + if (break_pos == line.length() && collapse_whitespace) + continue; + + line.push_back(collapse_whitespace ? ' ' : c); + break_pos = line.length(); + } + else { + line.push_back(c); + } + + if (line.length() > line_length) + { + if (break_pos > 0) { - out_line += ' '; - out_line += word; + // Break before last space, and skip that space + out->push_back(line.substr(0, break_pos - 1)); } else { - out->push_back(out_line); - out_line = word; + // Single word is too long, just break it + out->push_back(line.substr(0, line_length)); + break_pos = line_length; } + line = line.substr(break_pos); + break_pos = 0; } - if (out_line.length()) - out->push_back(out_line); } + if (line.length()) + out->push_back(line); + return true; } diff --git a/library/include/Debug.h b/library/include/Debug.h index 63811a51e..4cad178dc 100644 --- a/library/include/Debug.h +++ b/library/include/Debug.h @@ -326,7 +326,7 @@ public: DFHack::DebugCategory::LDEBUG, ## __VA_ARGS__) /*! - * Open a line for error level debug output if enabled + * Open a line for info level debug output if enabled * * Important debug messages when some rarely changed state changes. Example * would be when a debug category filtering level changes. diff --git a/library/include/MiscUtils.h b/library/include/MiscUtils.h index 7a5f050a2..764b11413 100644 --- a/library/include/MiscUtils.h +++ b/library/include/MiscUtils.h @@ -391,7 +391,8 @@ DFHACK_EXPORT std::string to_search_normalized(const std::string &str); DFHACK_EXPORT bool word_wrap(std::vector *out, const std::string &str, - size_t line_length = 80); + size_t line_length = 80, + bool collapse_whitespace = false); inline bool bits_match(unsigned required, unsigned ok, unsigned mask) { diff --git a/library/include/modules/Gui.h b/library/include/modules/Gui.h index 07672aaec..9b78e4984 100644 --- a/library/include/modules/Gui.h +++ b/library/include/modules/Gui.h @@ -129,10 +129,10 @@ 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); // Process an announcement exactly like DF would, which might result in no announcement - DFHACK_EXPORT int autoDFAnnouncement(df::report_init r, std::string message); - DFHACK_EXPORT int autoDFAnnouncement(df::report_init r, std::string message, bool log_failures); - DFHACK_EXPORT int autoDFAnnouncement(df::announcement_type type, df::coord pos, std::string message, int color = 7, bool bright = true, df::unit *unit1 = NULL, df::unit *unit2 = NULL, bool is_sparring = false, bool log_failures = false); - + DFHACK_EXPORT bool autoDFAnnouncement(df::report_init r, std::string message); + DFHACK_EXPORT bool autoDFAnnouncement(df::announcement_type type, df::coord pos, std::string message, int color = 7, bool bright = true, + df::unit *unit1 = NULL, df::unit *unit2 = NULL, bool is_sparring = false); + /* * Cursor and window coords */ diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index ce43df71f..0c59b0db2 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -37,6 +37,7 @@ using namespace std; #include "Error.h" #include "ModuleFactory.h" #include "Core.h" +#include "Debug.h" #include "PluginManager.h" #include "MiscUtils.h" using namespace DFHack; @@ -112,6 +113,11 @@ using namespace DFHack; #include "df/viewscreen_workshop_profilest.h" #include "df/world.h" +namespace DFHack +{ + DBG_DECLARE(core, gui, DebugCategory::LINFO); +} + using namespace df::enums; using df::global::gamemode; @@ -1707,22 +1713,28 @@ void Gui::showAutoAnnouncement( addCombatReportAuto(unit2, flags, id); } -int Gui::autoDFAnnouncement(df::report_init r, string message) +bool Gui::autoDFAnnouncement(df::report_init r, string message) { // Reverse-engineered from DF announcement code - - if (!world->unk_26a9a8) // TODO: world->show_announcements - return 1; + + if (!world->allow_announcements) + { + DEBUG(gui).print("Skipped announcement because world->allow_announcements is false:\n%s\n", message.c_str()); + return false; + } df::announcement_flags a_flags; if (is_valid_enum_item(r.type)) a_flags = df::global::d_init->announcements.flags[r.type]; else - return 2; + { + WARN(gui).print("Invalid announcement type:\n%s\n", message.c_str()); + return false; + } if (message.empty()) { Core::printerr("Empty announcement %u\n", r.type); // DF would print this to errorlog.txt - return 3; + return false; } // Check if the announcement will actually be announced @@ -1737,28 +1749,41 @@ int Gui::autoDFAnnouncement(df::report_init r, string message) if ((world->units.active.empty() || (r.unit1 != world->units.active[0] && r.unit2 != world->units.active[0])) && ((Maps::getTileDesignation(r.pos)->whole & 0x10) == 0x0)) // Adventure mode uses this bit to determine current visibility { - return 4; + DEBUG(gui).print("Adventure mode announcement not heard:\n%s\n", message.c_str()); + return false; } } } else { // Dwarf mode (or arena?) if ((r.unit1 != NULL || r.unit2 != NULL) && (r.unit1 == NULL || Units::isHidden(r.unit1)) && (r.unit2 == NULL || Units::isHidden(r.unit2))) - return 5; + { + DEBUG(gui).print("Dwarf mode announcement not heard:\n%s\n", message.c_str()); + return false; + } if (!a_flags.bits.D_DISPLAY) { if (a_flags.bits.UNIT_COMBAT_REPORT) { if (r.unit1 == NULL && r.unit2 == NULL) - return 6; + { + DEBUG(gui).print("Skipped UNIT_COMBAT_REPORT because it has no units:\n%s\n", message.c_str()); + return false; + } } else { if (!a_flags.bits.UNIT_COMBAT_REPORT_ALL_ACTIVE) - return 7; - if (!recent_report_any(r.unit1) && !recent_report_any(r.unit2)) - return 8; + { + DEBUG(gui).print("Skipped announcement not enabled for this game mode:\n%s\n", message.c_str()); + return false; + } + else if (!recent_report_any(r.unit1) && !recent_report_any(r.unit2)) + { + DEBUG(gui).print("Skipped UNIT_COMBAT_REPORT_ALL_ACTIVE because there's no active report:\n%s\n", message.c_str()); + return false; + } } } } @@ -1774,7 +1799,10 @@ int Gui::autoDFAnnouncement(df::report_init r, string message) parseReportString(results, message, line_length); if (results.empty()) - return 9; + { + DEBUG(gui).print("Skipped announcement because it was empty after parsing:\n%s\n", message.c_str()); + return false; + } // Check for repeat report int32_t repeat_count = check_repeat_report(results); @@ -1785,7 +1813,8 @@ int Gui::autoDFAnnouncement(df::report_init r, string message) world->status.display_timer = r.display_timer; Gui::writeToGamelog("x" + to_string(repeat_count + 1)); } - return 0; + DEBUG(gui).print("Announcement succeeded as repeat:\n%s\n", message.c_str()); + return true; } bool success = false; // only print to gamelog if report was used @@ -1864,66 +1893,25 @@ int Gui::autoDFAnnouncement(df::report_init r, string message) delete_old_reports(); - if (/*debug_gamelog &&*/ success) + if (/*debug_gamelog &&*/ success) // TODO: Add debug_gamelog to globals? + { + DEBUG(gui).print("Announcement succeeded and printed to gamelog.txt:\n%s\n", message.c_str()); Gui::writeToGamelog(message); - else if (success) - return 10; + } + /*else if (success) + { + DEBUG(gui).print("Announcement succeeded but skipped printing to gamelog.txt because debug_gamelog is false:\n%s\n", message.c_str()); + }*/ else - return 11; - - return 0; -} - -int Gui::autoDFAnnouncement(df::report_init r, string message, bool log_failures) -{ // Prints info about failed announcements to DFHack console if log_failures is true - int rv = autoDFAnnouncement(r, message); - - if (log_failures) { - switch (rv) - { - case 0: - break; // success - case 1: - Core::print("Skipped an announcement because world->show_announcements is false:\n%s\n", message.c_str()); - break; - case 2: - Core::printerr("Invalid announcement type!\n"); - break; - case 3: - break; // empty announcement, already handled - case 4: - Core::print("An adventure announcement occured, but nobody heard:\n%s\n", message.c_str()); - break; - case 5: - Core::print("An announcement occured, but nobody heard:\n%s\n", message.c_str()); - break; - case 6: - Core::print("Skipped a UNIT_COMBAT_REPORT because it has no units:\n%s\n", message.c_str()); - break; - case 7: - Core::print("Skipped an announcement not enabled for this game mode:\n%s\n", message.c_str()); - break; - case 8: - Core::print("Skipped an announcement because there's no active report:\n%s\n", message.c_str()); - break; - case 9: - Core::print("Skipped an announcement because it was empty after parsing:\n%s\n", message.c_str()); - break; - case 10: - Core::print("Report added but skipped printing to gamelog.txt because debug_gamelog is false.\n"); - break; - case 11: - Core::print("Report added but didn't qualify to be displayed anywhere:\n%s\n", message.c_str()); - break; - default: - Core::printerr("autoDFAnnouncement: Unexpected return value!\n"); - } + DEBUG(gui).print("Announcement succeeded internally but didn't qualify to be displayed anywhere:\n%s\n", message.c_str()); } - return rv; + + return true; } -int Gui::autoDFAnnouncement(df::announcement_type type, df::coord pos, std::string message, int color, bool bright, df::unit *unit1, df::unit *unit2, bool is_sparring, bool log_failures) +bool Gui::autoDFAnnouncement(df::announcement_type type, df::coord pos, std::string message, int color, + bool bright, df::unit *unit1, df::unit *unit2, bool is_sparring) { auto r = df::report_init(); r.type = type; @@ -1937,7 +1925,7 @@ int Gui::autoDFAnnouncement(df::announcement_type type, df::coord pos, std::stri if (Maps::isValidTilePos(pos)) r.zoom_type = report_zoom_type::Unit; - return autoDFAnnouncement(r, message, log_failures); + return autoDFAnnouncement(r, message); } df::viewscreen *Gui::getCurViewscreen(bool skip_dismissed) diff --git a/library/xml b/library/xml index 7ec5d8699..59075f42b 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 7ec5d86996269df5e01d64ea5bae67d0c29afd77 +Subproject commit 59075f42bbc77c354b5f815c5c1cce5bf48e76a5 diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index b8cd25860..ed9404b8e 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -87,7 +87,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(autoclothing autoclothing.cpp) dfhack_plugin(autodump autodump.cpp) dfhack_plugin(autofarm autofarm.cpp) - dfhack_plugin(autogems autogems.cpp LINK_LIBRARIES jsoncpp_lib_static) + dfhack_plugin(autogems autogems.cpp LINK_LIBRARIES jsoncpp_static) dfhack_plugin(autohauler autohauler.cpp) dfhack_plugin(autolabor autolabor.cpp) dfhack_plugin(automaterial automaterial.cpp LINK_LIBRARIES lua) @@ -109,7 +109,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(cursecheck cursecheck.cpp) dfhack_plugin(cxxrandom cxxrandom.cpp LINK_LIBRARIES lua) dfhack_plugin(deramp deramp.cpp) - dfhack_plugin(debug debug.cpp LINK_LIBRARIES jsoncpp_lib_static) + dfhack_plugin(debug debug.cpp LINK_LIBRARIES jsoncpp_static) dfhack_plugin(dig dig.cpp) dfhack_plugin(dig-now dig-now.cpp LINK_LIBRARIES lua) dfhack_plugin(digFlood digFlood.cpp) @@ -143,7 +143,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(mode mode.cpp) dfhack_plugin(mousequery mousequery.cpp) dfhack_plugin(nestboxes nestboxes.cpp) - dfhack_plugin(orders orders.cpp LINK_LIBRARIES jsoncpp_lib_static) + dfhack_plugin(orders orders.cpp LINK_LIBRARIES jsoncpp_static) dfhack_plugin(pathable pathable.cpp LINK_LIBRARIES lua) dfhack_plugin(petcapRemover petcapRemover.cpp) dfhack_plugin(plants plants.cpp) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index d6921f1ac..ba116c1c5 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -873,7 +873,7 @@ static void assign_labor(unit_labor::unit_labor labor, } int pool = labor_infos[labor].talent_pool(); - if (pool < 200 && candidates.size() > 1 && abs(pool) < candidates.size()) + if (pool < 200 && candidates.size() > 1 && size_t(abs(pool)) < candidates.size()) { // Sort in descending order std::sort(candidates.begin(), candidates.end(), [&](const int lhs, const int rhs) -> bool { diff --git a/plugins/command-prompt.cpp b/plugins/command-prompt.cpp index a987ea829..ba2fe0e87 100644 --- a/plugins/command-prompt.cpp +++ b/plugins/command-prompt.cpp @@ -1,23 +1,25 @@ -//command-prompt a one line command entry at the top of the screen for quick commands +// command-prompt: A one-line command entry at the top of the screen for quick commands #include "Core.h" +#include #include #include +#include #include -#include -#include #include +#include -#include #include +#include +#include #include #include +#include "df/enabler.h" +#include "df/graphic.h" #include "df/interface_key.h" #include "df/ui.h" -#include "df/graphic.h" -#include "df/enabler.h" using namespace DFHack; using namespace df::enums; @@ -36,8 +38,10 @@ class prompt_ostream:public buffered_color_ostream protected: void flush_proxy(); public: - prompt_ostream(viewscreen_commandpromptst* parent):parent_(parent){} - bool empty(){return buffer.empty();} + prompt_ostream(viewscreen_commandpromptst* parent) + : parent_(parent) + {} + bool empty() { return buffer.empty(); } }; class viewscreen_commandpromptst : public dfhack_viewscreen { public: @@ -48,7 +52,7 @@ public: } void render(); - void help() { } + void help() {} int8_t movies_okay() { return 0; } df::unit* getSelectedUnit() { return Gui::getAnyUnit(parent); } @@ -57,10 +61,11 @@ public: df::plant* getSelectedPlant() { return Gui::getAnyPlant(parent); } std::string getFocusString() { return "commandprompt"; } - viewscreen_commandpromptst(std::string entry):submitted(false), is_response(false) + viewscreen_commandpromptst(std::string entry) + : submitted(false), is_response(false) { - show_fps=gps->display_frames; - gps->display_frames=0; + show_fps = gps->display_frames; + gps->display_frames = 0; cursor_pos = entry.size(); frame = 0; history_idx = command_history.size(); @@ -76,7 +81,7 @@ public: } ~viewscreen_commandpromptst() { - gps->display_frames=show_fps; + gps->display_frames = show_fps; } void add_response(color_value v, std::string s) @@ -125,7 +130,7 @@ public: } protected: - std::list > responses; + std::list > responses; int cursor_pos; int history_idx; bool submitted; @@ -138,8 +143,8 @@ void prompt_ostream::flush_proxy() { if (buffer.empty()) return; - for(auto it=buffer.begin();it!=buffer.end();it++) - parent_->add_response(it->first,it->second); + for(auto it = buffer.begin(); it != buffer.end(); it++) + parent_->add_response(it->first, it->second); buffer.clear(); } void viewscreen_commandpromptst::render() @@ -154,25 +159,31 @@ void viewscreen_commandpromptst::render() auto dim = Screen::getWindowSize(); parent->render(); - if(is_response) + if (is_response) { - auto it=responses.begin(); - for(int i=0;isecond; - Screen::paintString(Screen::Pen(' ',it->first,0),0,i,cur_line.substr(0,cur_line.size()-1)); + std::vector lines; + word_wrap(&lines, response.second, dim.x); + for (auto &line : lines) + { + Screen::fillRect(Screen::Pen(' ', 7, 0), 0, y, dim.x, y); + Screen::paintString(Screen::Pen(' ', response.first, 0), 0, y, line); + if (++y >= dim.y) + return; + } } } else { std::string entry = get_entry(); - Screen::fillRect(Screen::Pen(' ', 7, 0),0,0,dim.x,0); - Screen::paintString(Screen::Pen(' ', 7, 0), 0, 0,"[DFHack]#"); + Screen::fillRect(Screen::Pen(' ', 7, 0), 0, 0, dim.x, 0); + Screen::paintString(Screen::Pen(' ', 7, 0), 0, 0, "[DFHack]#"); std::string cursor = (frame < enabler->gfps / 2) ? "_" : " "; - if(cursor_pos < (dim.x - 10)) + if (cursor_pos < dim.x - 10) { - Screen::paintString(Screen::Pen(' ', 7, 0), 10,0 , entry); + Screen::paintString(Screen::Pen(' ', 7, 0), 10, 0, entry); if (int16_t(entry.size()) > dim.x - 10) Screen::paintTile(Screen::Pen('\032', 7, 0), dim.x - 1, 0); if (cursor != " ") @@ -191,12 +202,12 @@ void viewscreen_commandpromptst::render() void viewscreen_commandpromptst::submit() { CoreSuspendClaimer suspend; - if(is_response) + if (is_response) { Screen::dismiss(this); return; } - if(submitted) + if (submitted) return; submitted = true; prompt_ostream out(this); @@ -204,11 +215,11 @@ void viewscreen_commandpromptst::submit() Screen::Hide hide_guard(this, Screen::Hide::RESTORE_AT_TOP); Core::getInstance().runCommand(out, get_entry()); } - if(out.empty() && responses.empty()) + if (out.empty() && responses.empty()) Screen::dismiss(this); else { - is_response=true; + is_response = true; } } void viewscreen_commandpromptst::feed(std::set *events) @@ -240,14 +251,14 @@ void viewscreen_commandpromptst::feed(std::set *events) for (auto it = events->begin(); it != events->end(); ++it) { auto key = *it; - if (key==interface_key::STRING_A000) //delete? + if (key == interface_key::STRING_A000) //delete? { - if(entry.size() && cursor_pos > 0) + if (entry.size() && cursor_pos > 0) { entry.erase(cursor_pos - 1, 1); cursor_pos--; } - if(size_t(cursor_pos) > entry.size()) + if (size_t(cursor_pos) > entry.size()) cursor_pos = entry.size(); continue; } @@ -261,34 +272,34 @@ void viewscreen_commandpromptst::feed(std::set *events) } } // Prevent number keys from moving cursor - if(events->count(interface_key::CURSOR_RIGHT)) + if (events->count(interface_key::CURSOR_RIGHT)) { cursor_pos++; if (size_t(cursor_pos) > entry.size()) cursor_pos = entry.size(); } - else if(events->count(interface_key::CURSOR_LEFT)) + else if (events->count(interface_key::CURSOR_LEFT)) { cursor_pos--; if (cursor_pos < 0) cursor_pos = 0; } - else if(events->count(interface_key::CURSOR_RIGHT_FAST)) + else if (events->count(interface_key::CURSOR_RIGHT_FAST)) { forward_word(); } - else if(events->count(interface_key::CURSOR_LEFT_FAST)) + else if (events->count(interface_key::CURSOR_LEFT_FAST)) { back_word(); } - else if(events->count(interface_key::CUSTOM_CTRL_A)) + else if (events->count(interface_key::CUSTOM_CTRL_A)) { cursor_pos = 0; } - else if(events->count(interface_key::CUSTOM_CTRL_E)) + else if (events->count(interface_key::CUSTOM_CTRL_E)) { cursor_pos = entry.size(); } - else if(events->count(interface_key::CURSOR_UP)) + else if (events->count(interface_key::CURSOR_UP)) { history_idx--; if (history_idx < 0) @@ -296,7 +307,7 @@ void viewscreen_commandpromptst::feed(std::set *events) entry = get_entry(); cursor_pos = entry.size(); } - else if(events->count(interface_key::CURSOR_DOWN)) + else if (events->count(interface_key::CURSOR_DOWN)) { if (size_t(history_idx) < command_history.size() - 1) { @@ -321,8 +332,8 @@ command_result show_prompt(color_ostream &out, std::vector & param return CR_OK; } std::string params; - for(size_t i=0;i(params), plugin_self); return CR_OK; } @@ -330,21 +341,23 @@ bool hotkey_allow_all(df::viewscreen *top) { return true; } -DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "command-prompt","Shows a command prompt on window.",show_prompt,hotkey_allow_all, - "command-prompt [entry] - shows a cmd prompt in df window. Entry is used for default prefix (e.g. ':lua')" + "command-prompt", "Shows a command prompt on window.", + show_prompt, hotkey_allow_all, + "command-prompt [entry] - shows a cmd prompt in df window." + " Entry is used for default prefix (e.g. ':lua')" )); return CR_OK; } -DFhackCExport command_result plugin_onstatechange (color_ostream &out, state_change_event e) +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event e) { return CR_OK; } -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +DFhackCExport command_result plugin_shutdown(color_ostream &out) { return CR_OK; } diff --git a/plugins/isoworld b/plugins/isoworld index e3c49ab01..3630c816d 160000 --- a/plugins/isoworld +++ b/plugins/isoworld @@ -1 +1 @@ -Subproject commit e3c49ab017da2dcbeaadccd10e56d07d8f03b4ca +Subproject commit 3630c816df6962b4594d46d1ae75974c36c11629 diff --git a/scripts b/scripts index b808050d4..e062237ee 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit b808050d4a3885aa0e250e726708b2b28fe28260 +Subproject commit e062237ee03a22d0d8b88b725ba3712e649f1bf6 From 4b21e7afb4ecb0b90cd4fd3a7c716ec2cfeb5e23 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Tue, 24 May 2022 03:52:33 -0700 Subject: [PATCH 16/32] Remove parseReportString from API (now utility fn) Implementations using `word_wrap()` are commented out pending changes to that function. --- docs/Lua API.rst | 11 +- docs/Removed.rst | 21 ++++ docs/changelog.txt | 7 +- library/include/modules/Gui.h | 3 +- library/lua/gui/widgets.lua | 39 ++++--- library/modules/Gui.cpp | 197 ++++++++++++++++++++++------------ library/xml | 2 +- plugins/confirm.cpp | 2 + plugins/lua/confirm.lua | 8 ++ scripts | 2 +- test/library/gui/widgets.lua | 18 ++++ 11 files changed, 224 insertions(+), 86 deletions(-) create mode 100644 test/library/gui/widgets.lua diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 7e1f45231..5a5737b93 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1095,7 +1095,7 @@ Announcements and a string and processes them just like DF does. Sometimes this means the announcement won't occur. Can also be built from parameters instead of a ``report_init``. Setting ``is_sparring`` to ``true`` means the report will be added to sparring logs (if applicable) rather than hunting or combat. Text is parsed using ``&`` as an escape character, with ``&r`` - being a newline, ``&&`` being just ``&``, and any other combination causing neither character to display. + adding a blank line (equivalent to ``\n \n``,) ``&&`` being just ``&``, and any other combination causing neither character to display. Other ~~~~~ @@ -3856,6 +3856,7 @@ Subclass of Widget; implements a simple edit field. Attributes: +:label_text: The optional text label displayed before the editable text. :text: The current contents of the field. :text_pen: The pen to draw the text with. :on_char: Input validation callback; used as ``on_char(new_char,text)``. @@ -3863,6 +3864,8 @@ Attributes: :on_change: Change notification callback; used as ``on_change(new_text,old_text)``. :on_submit: Enter key callback; if set the field will handle the key and call ``on_submit(text)``. :key: If specified, the field is disabled until this key is pressed. Must be given as a string. +:key_sep: If specified, will be used to customize how the activation key is + displayed. See ``token.key_sep`` in the ``Label`` documentation below. Label class ----------- @@ -3931,8 +3934,8 @@ containing newlines, or a table with the following possible fields: * ``token.key_sep = '...'`` Specifies the separator to place between the keybinding label produced - by ``token.key``, and the main text of the token. If the separator is - '()', the token is formatted as ``text..' ('..binding..')'``. Otherwise + by ``token.key``, and the main text of the token. If the separator starts with + '()', the token is formatted as ``text..' ('..binding..sep:sub(2)``. Otherwise it is simply ``binding..sep..text``. * ``token.enabled``, ``token.disabled`` @@ -4023,6 +4026,8 @@ a hotkey. It has the following attributes: :key: The hotkey keycode to display, e.g. ``'CUSTOM_A'``. +:key_sep: If specified, will be used to customize how the activation key is + displayed. See ``token.key_sep`` in the ``Label`` documentation. :label: The string (or a function that returns a string) to display after the hotkey. :on_activate: If specified, it is the callback that will be called whenever diff --git a/docs/Removed.rst b/docs/Removed.rst index 54e83a6fc..e94b04cf8 100644 --- a/docs/Removed.rst +++ b/docs/Removed.rst @@ -18,6 +18,27 @@ devel/unforbidall Replaced by the `unforbid` script. Run ``unforbid all --quiet`` to match the behavior of the original ``devel/unforbidall`` script. +.. _deteriorateclothes: + +deteriorateclothes +================== +Replaced by the new combined `deteriorate` script. Run +``deteriorate --types=clothes``. + +.. _deterioratecorpses: + +deterioratecorpses +================== +Replaced by the new combined `deteriorate` script. Run +``deteriorate --types=corpses``. + +.. _deterioratefood: + +deterioratefood +=============== +Replaced by the new combined `deteriorate` script. Run +``deteriorate --types=food``. + .. _digfort: digfort diff --git a/docs/changelog.txt b/docs/changelog.txt index dacf382fd..34b89d93e 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -38,16 +38,21 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## New Tweaks ## Fixes +- ``widgets.CycleHotkeyLabel``: allow initial option values to be specified as an index instead of an option value ## Misc Improvements +- `confirm`: added a confirmation dialog for removing manager orders - `dfhack-examples-guide`: refine food preparation orders and fix conditions for making jugs and pots in the ``basic`` manager orders ## Documentation ## API -- add functions reverse-engineered from announcement code: ``Gui::parseReportString``, ``Gui::autoDFAnnouncement``, ``Gui::pauseRecenter``, ``Gui::recenterViewscreen`` +- add functions reverse-engineered from announcement code: ``Gui::autoDFAnnouncement``, ``Gui::pauseRecenter``, ``Gui::recenterViewscreen`` ## Lua +- ``widgets.HotkeyLabel``: the ``key_sep`` string is now configurable +- ``widgets.EditField``: the ``key_sep`` string is now configurable +- ``widgets.EditField``: can now display an optional string label in addition to the activation key # 0.47.05-r5 diff --git a/library/include/modules/Gui.h b/library/include/modules/Gui.h index 9b78e4984..c7f857a1d 100644 --- a/library/include/modules/Gui.h +++ b/library/include/modules/Gui.h @@ -115,7 +115,6 @@ namespace DFHack // Low-level API that gives full control over announcements and reports DFHACK_EXPORT void writeToGamelog(std::string message); - DFHACK_EXPORT bool parseReportString(std::vector &out, const std::string &str, size_t line_length = 73); DFHACK_EXPORT int makeAnnouncement(df::announcement_type type, df::announcement_flags mode, df::coord pos, std::string message, int color = 7, bool bright = true); DFHACK_EXPORT bool addCombatReport(df::unit *unit, df::unit_report_type slot, int report_index); DFHACK_EXPORT bool addCombatReportAuto(df::unit *unit, df::announcement_flags mode, int report_index); @@ -131,7 +130,7 @@ namespace DFHack // Process an announcement exactly like DF would, which might result in no announcement DFHACK_EXPORT bool autoDFAnnouncement(df::report_init r, std::string message); DFHACK_EXPORT bool autoDFAnnouncement(df::announcement_type type, df::coord pos, std::string message, int color = 7, bool bright = true, - df::unit *unit1 = NULL, df::unit *unit2 = NULL, bool is_sparring = false); + df::unit *unit1 = NULL, df::unit *unit2 = NULL, bool is_sparring = false); /* * Cursor and window coords diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index b8782bd04..21e03370b 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -178,14 +178,27 @@ end EditField = defclass(EditField, Widget) EditField.ATTRS{ + label_text = DEFAULT_NIL, text = '', text_pen = DEFAULT_NIL, on_char = DEFAULT_NIL, on_change = DEFAULT_NIL, on_submit = DEFAULT_NIL, key = DEFAULT_NIL, + key_sep = DEFAULT_NIL, } +function EditField:init() + self:addviews{HotkeyLabel{frame={t=0,l=0}, + key=self.key, + key_sep=self.key_sep, + label=self.label_text}} +end + +function EditField:postUpdateLayout() + self.text_offset = self.subviews[1]:getTextWidth() +end + function EditField:onRenderBody(dc) dc:pen(self.text_pen or COLOR_LIGHTCYAN):fill(0,0,dc.width-1,0) @@ -194,16 +207,11 @@ function EditField:onRenderBody(dc) cursor = ' ' end local txt = self.text .. cursor - local dx = dc.x - if self.key then - dc:key_string(self.key, '') - end - dx = dc.x - dx - local max_width = dc.width - dx + local max_width = dc.width - self.text_offset if #txt > max_width then txt = string.char(27)..string.sub(txt, #txt-max_width+2) end - dc:string(txt) + dc:advance(self.text_offset):string(txt) end function EditField:onInput(keys) @@ -359,12 +367,13 @@ function render_text(obj,dc,x0,y0,pen,dpen,disabled) x = x + #keystr - if sep == '()' then + if sep:startswith('()') then if dc then dc:string(text) - dc:string(' ('):string(keystr,keypen):string(')') + dc:string(' ('):string(keystr,keypen) + dc:string(sep:sub(2)) end - x = x + 3 + x = x + 1 + #sep else if dc then dc:string(keystr,keypen):string(sep):string(text) @@ -605,13 +614,14 @@ HotkeyLabel = defclass(HotkeyLabel, Label) HotkeyLabel.ATTRS{ key=DEFAULT_NIL, + key_sep=': ', label=DEFAULT_NIL, on_activate=DEFAULT_NIL, } function HotkeyLabel:init() - self:setText{{key=self.key, key_sep=': ', text=self.label, - on_activate=self.on_activate}} + self:setText{{key=self.key, key_sep=self.key_sep, text=self.label, + on_activate=self.on_activate}} end ---------------------- @@ -637,6 +647,11 @@ function CycleHotkeyLabel:init() break end end + if not self.option_idx then + if self.options[self.initial_option] then + self.option_idx = self.initial_option + end + end if not self.option_idx then error(('cannot find option with value or index: "%s"') :format(self.initial_option)) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 0c59b0db2..828c2632f 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -1368,82 +1368,150 @@ DFHACK_EXPORT void Gui::writeToGamelog(std::string message) fseed.close(); } -bool Gui::parseReportString(std::vector &out, const std::string &str, size_t line_length) -{ // out vector will contain strings cut to line_length, avoiding cutting up words - // Reverse-engineered from DF announcement code, fixes applied +namespace +{ // Utility functions for reports + /*bool parseReportString(std::vector *out, const std::string &str, size_t line_length = 73) + { + if (str.empty() || line_length == 0) + return false; - if (str.empty() || line_length == 0) - return false; - out.clear(); + string parsed = ""; + size_t i = 0; - bool ignore_space = false; - string current_line = ""; - size_t iter = 0; - do - { - if (ignore_space) + while (i < str.length()) { - if (str[iter] == ' ') - continue; - ignore_space = false; + if (str[i] == '&') // escape character + { + i++; // ignore the '&' itself + if (i >= str.length()) + break; + + if (str[i] == 'r') // "&r" adds a blank line + parsed += "\n \n"; // DF adds a line with a space for some reason + else if (str[i] == '&') // "&&" is '&' + parsed += "&"; + // else next char is ignored + } + else + { + parsed += str[i]; + } + i++; } - if (str[iter] == '&') // escape character - { - iter++; // ignore the '&' itself - if (iter >= str.length()) - break; + return word_wrap(out, parsed, line_length, true); + }*/ + /*bool parseReportString(std::vector *out, const std::string &str, size_t line_length = 73) + { + if (str.empty() || line_length == 0) + return false; + + string parsed = ""; + size_t i = 0; - if (str[iter] == 'r') // "&r" starts new line + while (i < str.length()) + { + if (str[i] == '&') // escape character { - if (!current_line.empty()) + i++; // ignore the '&' itself + if (i >= str.length()) + break; + + if (str[i] == 'r') // "&r" adds a blank line { - out.push_back(string(current_line)); - current_line = ""; + word_wrap(out, parsed, line_length, true); + out->push_back(" "); // DF adds a line with a space for some reason + parsed = ""; } - out.push_back(" "); - continue; // don't add 'r' to current_line + else if (str[i] == '&') // "&&" is '&' + parsed += "&"; + // else next char is ignored } - else if (str[iter] != '&') - { // not "&&", don't add character to current_line - continue; + else + { + parsed += str[i]; } + i++; } - current_line += str[iter]; - if (current_line.length() > line_length) - { - size_t i = current_line.length(); // start of current word - size_t j; // end of previous word - while (--i > 0 && current_line[i] != ' '); // find start of current word + if (parsed != "") + word_wrap(out, parsed, line_length, true); + + return true; + }*/ + bool parseReportString(std::vector *out, const std::string &str, size_t line_length) + { // out vector will contain strings cut to line_length, avoiding cutting up words + // Reverse-engineered from DF announcement code, fixes applied + + if (str.empty() || line_length == 0) + return false; - if (i == 0) - { // need to push at least one char - j = i = line_length; // last char ends up on next line + bool ignore_space = false; + string current_line = ""; + size_t iter = 0; + do + { + if (ignore_space) + { + if (str[iter] == ' ') + continue; + ignore_space = false; } - else + + if (str[iter] == '&') // escape character { - j = i; - while (j > 1 && current_line[j - 1] == ' ') - j--; // consume excess spaces at the split point + iter++; // ignore the '&' itself + if (iter >= str.length()) + break; + + if (str[iter] == 'r') // "&r" adds a blank line + { + if (!current_line.empty()) + { + out->push_back(string(current_line)); + current_line = ""; + } + out->push_back(" "); // DF adds a line with a space for some reason + continue; // don't add 'r' to current_line + } + else if (str[iter] != '&') + { // not "&&", don't add character to current_line + continue; + } } - out.push_back(current_line.substr(0, j)); // push string before j - if (current_line[i] == ' ') - i++; // don't keep this space - current_line.erase(0, i); // current_line now starts at last word or is empty - ignore_space = current_line.empty(); // ignore leading spaces on new line - } - } while (++iter < str.length()); + current_line += str[iter]; + if (current_line.length() > line_length) + { + size_t i = current_line.length(); // start of current word + size_t j; // end of previous word + while (--i > 0 && current_line[i] != ' '); // find start of current word - if (!current_line.empty()) - out.push_back(current_line); + if (i == 0) + { // need to push at least one char + j = i = line_length; // last char ends up on next line + } + else + { + j = i; + while (j > 1 && current_line[j - 1] == ' ') + j--; // consume excess spaces at the split point + } + out->push_back(current_line.substr(0, j)); // push string before j - return true; -} + if (current_line[i] == ' ') + i++; // don't keep this space + current_line.erase(0, i); // current_line now starts at last word or is empty + ignore_space = current_line.empty(); // ignore leading spaces on new line + } + } while (++iter < str.length()); + + if (!current_line.empty()) + out->push_back(current_line); + + return true; + } -namespace -{ // Utility functions for reports bool recent_report(df::unit *unit, df::unit_report_type slot) { if (unit && !unit->reports.log[slot].empty() && @@ -1755,7 +1823,7 @@ bool Gui::autoDFAnnouncement(df::report_init r, string message) } } else - { // Dwarf mode (or arena?) + { // Dwarf mode if ((r.unit1 != NULL || r.unit2 != NULL) && (r.unit1 == NULL || Units::isHidden(r.unit1)) && (r.unit2 == NULL || Units::isHidden(r.unit2))) { DEBUG(gui).print("Dwarf mode announcement not heard:\n%s\n", message.c_str()); @@ -1796,16 +1864,16 @@ bool Gui::autoDFAnnouncement(df::report_init r, string message) vector results; size_t line_length = (r.speaker_id == -1) ? (init->display.grid_x - 7) : (init->display.grid_x - 10); - parseReportString(results, message, line_length); + parseReportString(&results, message, line_length); - if (results.empty()) + if (results.empty()) // DF doesn't do this check { DEBUG(gui).print("Skipped announcement because it was empty after parsing:\n%s\n", message.c_str()); return false; } // Check for repeat report - int32_t repeat_count = check_repeat_report(results); + int32_t repeat_count = check_repeat_report(results); // Does nothing outside dwarf mode if (repeat_count > 0) { if (a_flags.bits.D_DISPLAY) @@ -1818,7 +1886,7 @@ bool Gui::autoDFAnnouncement(df::report_init r, string message) } bool success = false; // only print to gamelog if report was used - size_t new_report_index = world->status.reports.size(); + size_t new_report_index = world->status.reports.size(); // we need this for addCombatReport for (size_t i = 0; i < results.size(); i++) { // Generate report entries for each line auto new_report = new df::report(); @@ -1854,7 +1922,7 @@ bool Gui::autoDFAnnouncement(df::report_init r, string message) } } - if (*gamemode == game_mode::DWARF) + if (*gamemode == game_mode::DWARF) // DF does this inside the previous loop, but we're using addCombatReport instead { if (a_flags.bits.UNIT_COMBAT_REPORT) { @@ -1902,7 +1970,7 @@ bool Gui::autoDFAnnouncement(df::report_init r, string message) { DEBUG(gui).print("Announcement succeeded but skipped printing to gamelog.txt because debug_gamelog is false:\n%s\n", message.c_str()); }*/ - else + else // not sure if this can actually happen; our results.empty() check handles the one edge case I can think of that would get this far { DEBUG(gui).print("Announcement succeeded internally but didn't qualify to be displayed anywhere:\n%s\n", message.c_str()); } @@ -1911,7 +1979,7 @@ bool Gui::autoDFAnnouncement(df::report_init r, string message) } bool Gui::autoDFAnnouncement(df::announcement_type type, df::coord pos, std::string message, int color, - bool bright, df::unit *unit1, df::unit *unit2, bool is_sparring) + bool bright, df::unit *unit1, df::unit *unit2, bool is_sparring) { auto r = df::report_init(); r.type = type; @@ -1922,9 +1990,6 @@ bool Gui::autoDFAnnouncement(df::announcement_type type, df::coord pos, std::str r.unit2 = unit2; r.flags.bits.hostile_combat = !is_sparring; - if (Maps::isValidTilePos(pos)) - r.zoom_type = report_zoom_type::Unit; - return autoDFAnnouncement(r, message); } diff --git a/library/xml b/library/xml index 59075f42b..a59495e8f 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 59075f42bbc77c354b5f815c5c1cce5bf48e76a5 +Subproject commit a59495e8f72115909772e6df20a7b9dec272f14c diff --git a/plugins/confirm.cpp b/plugins/confirm.cpp index a57ec45dd..6a2798cb4 100644 --- a/plugins/confirm.cpp +++ b/plugins/confirm.cpp @@ -18,6 +18,7 @@ #include "df/general_ref.h" #include "df/general_ref_contained_in_itemst.h" #include "df/viewscreen_dwarfmodest.h" +#include "df/viewscreen_jobmanagementst.h" #include "df/viewscreen_justicest.h" #include "df/viewscreen_layer_militaryst.h" #include "df/viewscreen_locationsst.h" @@ -481,6 +482,7 @@ DEFINE_CONFIRMATION(note_delete, viewscreen_dwarfmodest); DEFINE_CONFIRMATION(route_delete, viewscreen_dwarfmodest); DEFINE_CONFIRMATION(location_retire, viewscreen_locationsst); DEFINE_CONFIRMATION(convict, viewscreen_justicest); +DEFINE_CONFIRMATION(order_remove, viewscreen_jobmanagementst); DFhackCExport command_result plugin_init (color_ostream &out, vector &commands) { diff --git a/plugins/lua/confirm.lua b/plugins/lua/confirm.lua index 13b28962a..e36a4fb86 100644 --- a/plugins/lua/confirm.lua +++ b/plugins/lua/confirm.lua @@ -219,6 +219,14 @@ function convict.get_message() "This action is irreversible." end +order_remove = defconf('order-remove') +function order_remove.intercept_key(key) + return key == keys.MANAGER_REMOVE and + not screen.in_max_workshops +end +order_remove.title = "Remove manager order" +order_remove.message = "Are you sure you want to remove this order?" + -- End of confirmation definitions function check() diff --git a/scripts b/scripts index e062237ee..05d46b32a 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit e062237ee03a22d0d8b88b725ba3712e649f1bf6 +Subproject commit 05d46b32a3aff4f5f98534fdccfbf9ae88dd31a3 diff --git a/test/library/gui/widgets.lua b/test/library/gui/widgets.lua new file mode 100644 index 000000000..1eed30e4f --- /dev/null +++ b/test/library/gui/widgets.lua @@ -0,0 +1,18 @@ +local widgets = require('gui.widgets') + +function test.togglehotkeylabel() + local toggle = widgets.ToggleHotkeyLabel{} + expect.true_(toggle:getOptionValue()) + toggle:cycle() + expect.false_(toggle:getOptionValue()) + toggle:cycle() + expect.true_(toggle:getOptionValue()) +end + +function test.togglehotkeylabel_default_value() + local toggle = widgets.ToggleHotkeyLabel{initial_option=2} + expect.false_(toggle:getOptionValue()) + + toggle = widgets.ToggleHotkeyLabel{initial_option=false} + expect.false_(toggle:getOptionValue()) +end From 2b294318060c0251831ccc42d01f678eca04f988 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sat, 28 May 2022 12:35:49 -0700 Subject: [PATCH 17/32] More fixes * Use word_wrap() * add_proper_report utility fn; have addCombatReportAuto use this * Update Lua API.rst * Update Gui.cpp --- README.md | 6 +- ci/test.lua | 12 +- depends/libexpat | 2 +- docs/Lua API.rst | 26 ++-- library/lua/gui/dialogs.lua | 8 +- library/lua/gui/widgets.lua | 12 +- library/lua/test_util/mock.lua | 52 ++++++- library/modules/Gui.cpp | 218 +++++++---------------------- library/xml | 2 +- scripts | 2 +- test/library/gui/widgets.Label.lua | 94 +++++++++++++ test/library/test_util/mock.lua | 37 ++++- 12 files changed, 273 insertions(+), 198 deletions(-) create mode 100644 test/library/gui/widgets.Label.lua diff --git a/README.md b/README.md index 15f13dbc9..fa6c343e1 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ DFHack is a Dwarf Fortress memory access library, distributed with scripts and plugins implementing a wide variety of useful functions and tools. -The full documentation [is available online here](https://dfhack.readthedocs.org), -from the README.html page in the DFHack distribution, or as raw text in the `./docs` folder. +The full documentation [is available online here](https://dfhack.readthedocs.org). +It is also accessible via the README.html page in the DFHack distribution or as raw text in the `./docs` folder. If you're an end-user, modder, or interested in contributing to DFHack - go read those docs. -If that's unclear, or you need more help checkout our [support page](https://docs.dfhack.org/en/latest/docs/Support.html) for up-to-date options. +If the docs are unclear or you need more help, please check out our [support page](https://docs.dfhack.org/en/latest/docs/Support.html) for ways to contact the DFHack developers. diff --git a/ci/test.lua b/ci/test.lua index 9a4a33ef2..aca78e851 100644 --- a/ci/test.lua +++ b/ci/test.lua @@ -240,7 +240,7 @@ end -- output doesn't trigger its own dfhack.printerr usage detection (see -- detect_printerr below) local orig_printerr = dfhack.printerr -local function wrap_expect(func, private) +local function wrap_expect(func, private, path) return function(...) private.checks = private.checks + 1 local ret = {func(...)} @@ -269,7 +269,7 @@ local function wrap_expect(func, private) end -- Skip any frames corresponding to C calls, or Lua functions defined in another file -- these could include pcall(), with_finalize(), etc. - if info.what == 'Lua' and info.short_src == caller_src then + if info.what == 'Lua' and (info.short_src == caller_src or info.short_src == path) then orig_printerr((' at %s:%d'):format(info.short_src, info.currentline)) end frame = frame + 1 @@ -278,7 +278,7 @@ local function wrap_expect(func, private) end end -local function build_test_env() +local function build_test_env(path) local env = { test = utils.OrderedTable(), -- config values can be overridden in the test file to define @@ -309,7 +309,7 @@ local function build_test_env() checks_ok = 0, } for name, func in pairs(expect) do - env.expect[name] = wrap_expect(func, private) + env.expect[name] = wrap_expect(func, private, path) end setmetatable(env, {__index = _G}) return env, private @@ -345,9 +345,9 @@ local function finish_tests(done_command) end local function load_tests(file, tests) - local short_filename = file:sub((file:find('test') or -4)+5, -1) + local short_filename = file:sub((file:find('test') or -4) + 5, -1) print('Loading file: ' .. short_filename) - local env, env_private = build_test_env() + local env, env_private = build_test_env(file) local code, err = loadfile(file, 't', env) if not code then dfhack.printerr('Failed to load file: ' .. tostring(err)) diff --git a/depends/libexpat b/depends/libexpat index 3e877cbb3..6ac8628a3 160000 --- a/depends/libexpat +++ b/depends/libexpat @@ -1 +1 @@ -Subproject commit 3e877cbb3c9bc8f22946053e70490d2e5431f4d5 +Subproject commit 6ac8628a3c7a1677b27fb007db96f665b684a183 diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 5a5737b93..8ec347bef 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1007,17 +1007,19 @@ Fortress mode * ``dfhack.gui.pauseRecenter(pos[,pause])`` ``dfhack.gui.pauseRecenter(x,y,z[,pause])`` - Same as ``resetDwarfmodeView``, but also recenter if ``x`` isn't ``-30000``, and respects - RECENTER_INTERFACE_SHUTDOWN_MS (the delay before input is recognized when a recenter occurs) in DF's init.txt. + Same as ``resetDwarfmodeView``, but also recenter if ``x`` isn't ``-30000``. Respects + ``RECENTER_INTERFACE_SHUTDOWN_MS`` in DF's ``init.txt`` (the delay before input is recognized when a recenter occurs.) * ``dfhack.gui.recenterViewscreen(pos[,zoom])`` ``dfhack.gui.recenterViewscreen(x,y,z[,zoom])`` ``dfhack.gui.recenterViewscreen([zoom])`` Recenter the view on a position using a specific zoom type. If no position is given, - recenter on ``df.global.cursor``. Zoom types are ``df.report_zoom_type`` (see: `enum definition `_), - where ``Generic`` skips recentering and enforces valid view bounds (the same as x = -30000,) ``Item`` brings - the position onscreen without centering, and ``Unit`` centers the screen on the position. Default zoom type is Item. + recenter on ``df.global.cursor``. Zoom types are ``df.report_zoom_type`` + (see: `enum definition `_), + where ``df.report_zoom_type.Generic`` skips recentering and enforces valid view bounds (the same as x = -30000,) + ``df.report_zoom_type.Item`` brings the position onscreen without centering, and + ``df.report_zoom_type.Unit`` centers the screen on the position. Default zoom type is ``df.report_zoom_type.Item``. * ``dfhack.gui.revealInDwarfmodeMap(pos)`` @@ -1091,11 +1093,15 @@ Announcements * ``dfhack.gui.autoDFAnnouncement(report,text)`` ``dfhack.gui.autoDFAnnouncement(type,pos,text,color[,is_bright,unit1,unit2,is_sparring])`` - Takes a ``df.report_init`` (see: `structure definition `_) - and a string and processes them just like DF does. Sometimes this means the announcement won't occur. - Can also be built from parameters instead of a ``report_init``. Setting ``is_sparring`` to ``true`` means the report - will be added to sparring logs (if applicable) rather than hunting or combat. Text is parsed using ``&`` as an escape character, with ``&r`` - adding a blank line (equivalent to ``\n \n``,) ``&&`` being just ``&``, and any other combination causing neither character to display. + Takes a ``df.report_init`` (see: `structure definition `_) + and a string and processes them just like DF does. Can also be built from parameters instead of a ``report_init``. + Setting ``is_sparring`` to *true* means the report will be added to sparring logs (if applicable) rather than hunting or combat. + + The announcement will not display if units are involved and the player can't see them (or hear, for adventure mode sound announcement types.) + Text is parsed using ``&`` as an escape character, with ``&r`` adding a blank line (equivalent to ``\n \n``,) + ``&&`` being just ``&``, and any other combination causing neither character to display. + + If you want a guaranteed announcement without parsing, use ``dfhack.gui.showAutoAnnouncement`` instead. Other ~~~~~ diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index 51f346bbd..952e8f734 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -36,7 +36,13 @@ end function MessageBox:getWantedFrameSize() local label = self.subviews.label local width = math.max(self.frame_width or 0, 20, #(self.frame_title or '') + 4) - return math.max(width, label:getTextWidth()), label:getTextHeight() + local text_area_width = label:getTextWidth() + if label.frame_inset then + -- account for scroll icons + text_area_width = text_area_width + (label.frame_inset.l or 0) + text_area_width = text_area_width + (label.frame_inset.r or 0) + end + return math.max(width, text_area_width), label:getTextHeight() end function MessageBox:onRenderFrame(dc,rect) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 21e03370b..c6e84d7a1 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -478,8 +478,16 @@ function Label:render_scroll_icons(dc, x, y1, y2) end end -function Label:postComputeFrame() - self:update_scroll_inset() +function Label:computeFrame(parent_rect) + local frame_rect,body_rect = Label.super.computeFrame(self, parent_rect) + + self.frame_rect = frame_rect + self.frame_body = parent_rect:viewport(body_rect or frame_rect) + + self:update_scroll_inset() -- frame_body is now set + + -- recalc with updated frame_inset + return Label.super.computeFrame(self, parent_rect) end function Label:preUpdateLayout() diff --git a/library/lua/test_util/mock.lua b/library/lua/test_util/mock.lua index c60646b77..8d253cc10 100644 --- a/library/lua/test_util/mock.lua +++ b/library/lua/test_util/mock.lua @@ -32,12 +32,17 @@ function _patch_impl(patches_raw, callback, restore_only) end --[[ + +Replaces `table[key]` with `value`, calls `callback()`, then restores the +original value of `table[key]`. + Usage: patch(table, key, value, callback) patch({ {table, key, value}, {table2, key2, value2}, }, callback) + ]] function mock.patch(...) local args = {...} @@ -57,12 +62,18 @@ function mock.patch(...) end --[[ + +Restores the original value of `table[key]` after calling `callback()`. + +Equivalent to: patch(table, key, table[key], callback) + Usage: restore(table, key, callback) restore({ {table, key}, {table2, key2}, }, callback) + ]] function mock.restore(...) local args = {...} @@ -81,9 +92,19 @@ function mock.restore(...) return _patch_impl(patches, callback, true) end -function mock.func(...) +--[[ + +Returns a callable object that tracks the arguments it is called with, then +passes those arguments to `callback()`. + +The returned object has the following properties: +- `call_count`: the number of times the object has been called +- `call_args`: a table of function arguments (shallow-copied) corresponding + to each time the object was called + +]] +function mock.observe_func(callback) local f = { - return_values = {...}, call_count = 0, call_args = {}, } @@ -101,11 +122,36 @@ function mock.func(...) end end table.insert(self.call_args, args) - return table.unpack(self.return_values) + return callback(...) end, }) return f end +--[[ + +Returns a callable object similar to `mock.observe_func()`, but which when +called, only returns the given `return_value`(s) with no additional side effects. + +Intended for use by `patch()`. + +Usage: + func(return_value [, return_value2 ...]) + +See `observe_func()` for a description of the return value. + +The return value also has an additional `return_values` field, which is a table +of values returned when the object is called. This can be modified. + +]] +function mock.func(...) + local f + f = mock.observe_func(function() + return table.unpack(f.return_values) + end) + f.return_values = {...} + return f +end + return mock diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 828c2632f..0dd50414e 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -1370,46 +1370,15 @@ DFHACK_EXPORT void Gui::writeToGamelog(std::string message) namespace { // Utility functions for reports - /*bool parseReportString(std::vector *out, const std::string &str, size_t line_length = 73) - { + bool parseReportString(std::vector *out, const std::string &str, size_t line_length = 73) + { // parse a string into output strings like DF does for reports if (str.empty() || line_length == 0) return false; - string parsed = ""; + string parsed; size_t i = 0; - while (i < str.length()) - { - if (str[i] == '&') // escape character - { - i++; // ignore the '&' itself - if (i >= str.length()) - break; - - if (str[i] == 'r') // "&r" adds a blank line - parsed += "\n \n"; // DF adds a line with a space for some reason - else if (str[i] == '&') // "&&" is '&' - parsed += "&"; - // else next char is ignored - } - else - { - parsed += str[i]; - } - i++; - } - - return word_wrap(out, parsed, line_length, true); - }*/ - /*bool parseReportString(std::vector *out, const std::string &str, size_t line_length = 73) - { - if (str.empty() || line_length == 0) - return false; - - string parsed = ""; - size_t i = 0; - - while (i < str.length()) + do { if (str[i] == '&') // escape character { @@ -1419,95 +1388,21 @@ namespace if (str[i] == 'r') // "&r" adds a blank line { - word_wrap(out, parsed, line_length, true); + word_wrap(out, parsed, line_length, false, true); out->push_back(" "); // DF adds a line with a space for some reason - parsed = ""; + parsed.clear(); } else if (str[i] == '&') // "&&" is '&' - parsed += "&"; + parsed.push_back('&'); // else next char is ignored } else - { - parsed += str[i]; - } - i++; + parsed.push_back(str[i]); } + while (++i < str.length()); - if (parsed != "") - word_wrap(out, parsed, line_length, true); - - return true; - }*/ - bool parseReportString(std::vector *out, const std::string &str, size_t line_length) - { // out vector will contain strings cut to line_length, avoiding cutting up words - // Reverse-engineered from DF announcement code, fixes applied - - if (str.empty() || line_length == 0) - return false; - - bool ignore_space = false; - string current_line = ""; - size_t iter = 0; - do - { - if (ignore_space) - { - if (str[iter] == ' ') - continue; - ignore_space = false; - } - - if (str[iter] == '&') // escape character - { - iter++; // ignore the '&' itself - if (iter >= str.length()) - break; - - if (str[iter] == 'r') // "&r" adds a blank line - { - if (!current_line.empty()) - { - out->push_back(string(current_line)); - current_line = ""; - } - out->push_back(" "); // DF adds a line with a space for some reason - continue; // don't add 'r' to current_line - } - else if (str[iter] != '&') - { // not "&&", don't add character to current_line - continue; - } - } - - current_line += str[iter]; - if (current_line.length() > line_length) - { - size_t i = current_line.length(); // start of current word - size_t j; // end of previous word - while (--i > 0 && current_line[i] != ' '); // find start of current word - - if (i == 0) - { // need to push at least one char - j = i = line_length; // last char ends up on next line - } - else - { - j = i; - while (j > 1 && current_line[j - 1] == ' ') - j--; // consume excess spaces at the split point - } - out->push_back(current_line.substr(0, j)); // push string before j - - if (current_line[i] == ' ') - i++; // don't keep this space - current_line.erase(0, i); // current_line now starts at last word or is empty - ignore_space = current_line.empty(); // ignore leading spaces on new line - } - } while (++iter < str.length()); - - if (!current_line.empty()) - out->push_back(current_line); + if (parsed.length()) + word_wrap(out, parsed, line_length, false, true); return true; } @@ -1549,7 +1444,7 @@ namespace } int32_t check_repeat_report(vector &results) - { + { // returns the new repeat count, else 0 if (*gamemode == game_mode::DWARF && !results.empty() && world->status.reports.size() >= results.size()) { auto &reports = world->status.reports; @@ -1697,6 +1592,19 @@ bool Gui::addCombatReport(df::unit *unit, df::unit_report_type slot, int report_ return true; } +namespace +{ // An additional utility function for reports + bool add_proper_report(df::unit *unit, bool is_sparring, int report_index) + { + if (is_sparring) + return addCombatReport(unit, unit_report_type::Sparring, report_index); + else if (unit->job.current_job != NULL && unit->job.current_job->job_type == job_type::Hunt) + return addCombatReport(unit, unit_report_type::Hunting, report_index); + else + return addCombatReport(unit, unit_report_type::Combat, report_index); + } +} + bool Gui::addCombatReportAuto(df::unit *unit, df::announcement_flags mode, int report_index) { using df::global::world; @@ -1710,14 +1618,7 @@ bool Gui::addCombatReportAuto(df::unit *unit, df::announcement_flags mode, int r bool ok = false; if (mode.bits.UNIT_COMBAT_REPORT) - { - if (unit->flags2.bits.sparring) - ok |= addCombatReport(unit, unit_report_type::Sparring, report_index); - else if (unit->job.current_job && unit->job.current_job->job_type == job_type::Hunt) - ok |= addCombatReport(unit, unit_report_type::Hunting, report_index); - else - ok |= addCombatReport(unit, unit_report_type::Combat, report_index); - } + ok |= add_proper_report(unit, unit->flags2.bits.sparring, report_index); if (mode.bits.UNIT_COMBAT_REPORT_ALL_ACTIVE) { @@ -1783,28 +1684,24 @@ void Gui::showAutoAnnouncement( bool Gui::autoDFAnnouncement(df::report_init r, string message) { // Reverse-engineered from DF announcement code - if (!world->allow_announcements) { DEBUG(gui).print("Skipped announcement because world->allow_announcements is false:\n%s\n", message.c_str()); return false; - } - - df::announcement_flags a_flags; - if (is_valid_enum_item(r.type)) - a_flags = df::global::d_init->announcements.flags[r.type]; - else + } + else if (!is_valid_enum_item(r.type)) { WARN(gui).print("Invalid announcement type:\n%s\n", message.c_str()); return false; } - - if (message.empty()) + else if (message.empty()) { Core::printerr("Empty announcement %u\n", r.type); // DF would print this to errorlog.txt return false; } + df::announcement_flags a_flags = df::global::d_init->announcements.flags[r.type]; + // Check if the announcement will actually be announced if (*gamemode == game_mode::ADVENTURE) { @@ -1814,11 +1711,13 @@ bool Gui::autoDFAnnouncement(df::report_init r, string message) r.type != announcement_type::CONFLICT_CONVERSATION && r.type != announcement_type::MECHANISM_SOUND) { // If not sound, make sure we can see pos - if ((world->units.active.empty() || (r.unit1 != world->units.active[0] && r.unit2 != world->units.active[0])) && - ((Maps::getTileDesignation(r.pos)->whole & 0x10) == 0x0)) // Adventure mode uses this bit to determine current visibility - { - DEBUG(gui).print("Adventure mode announcement not heard:\n%s\n", message.c_str()); - return false; + if (world->units.active.empty() || (r.unit1 != world->units.active[0] && r.unit2 != world->units.active[0])) + { // Adventure mode reuses a dwarf mode digging designation bit to determine current visibility + if (!Maps::isValidTilePos(r.pos) || (Maps::getTileDesignation(r.pos)->whole & 0x10) == 0x0) + { + DEBUG(gui).print("Adventure mode announcement not detected:\n%s\n", message.c_str()); + return false; + } } } } @@ -1826,7 +1725,7 @@ bool Gui::autoDFAnnouncement(df::report_init r, string message) { // Dwarf mode if ((r.unit1 != NULL || r.unit2 != NULL) && (r.unit1 == NULL || Units::isHidden(r.unit1)) && (r.unit2 == NULL || Units::isHidden(r.unit2))) { - DEBUG(gui).print("Dwarf mode announcement not heard:\n%s\n", message.c_str()); + DEBUG(gui).print("Dwarf mode announcement not detected:\n%s\n", message.c_str()); return false; } @@ -1927,24 +1826,10 @@ bool Gui::autoDFAnnouncement(df::report_init r, string message) if (a_flags.bits.UNIT_COMBAT_REPORT) { if (r.unit1 != NULL) - { - if (r.flags.bits.hostile_combat) - success |= addCombatReport(r.unit1, unit_report_type::Combat, new_report_index); - else if (r.unit1->job.current_job != NULL && r.unit1->job.current_job->job_type == job_type::Hunt) - success |= addCombatReport(r.unit1, unit_report_type::Hunting, new_report_index); - else - success |= addCombatReport(r.unit1, unit_report_type::Sparring, new_report_index); - } + success |= add_proper_report(r.unit1, !r.flags.bits.hostile_combat, new_report_index); if (r.unit2 != NULL) - { - if (r.flags.bits.hostile_combat) - success |= addCombatReport(r.unit2, unit_report_type::Combat, new_report_index); - else if (r.unit2->job.current_job != NULL && r.unit2->job.current_job->job_type == job_type::Hunt) - success |= addCombatReport(r.unit2, unit_report_type::Hunting, new_report_index); - else - success |= addCombatReport(r.unit2, unit_report_type::Sparring, new_report_index); - } + success |= add_proper_report(r.unit2, !r.flags.bits.hostile_combat, new_report_index); } if (a_flags.bits.UNIT_COMBAT_REPORT_ALL_ACTIVE) @@ -1953,6 +1838,7 @@ bool Gui::autoDFAnnouncement(df::report_init r, string message) { if (recent_report(r.unit1, slot)) success |= addCombatReport(r.unit1, slot, new_report_index); + if (recent_report(r.unit2, slot)) success |= addCombatReport(r.unit2, slot, new_report_index); } @@ -2044,9 +1930,7 @@ df::coord Gui::getCursorPos() } void Gui::recenterViewscreen(int32_t x, int32_t y, int32_t z, df::report_zoom_type zoom) -{ - // Reverse-engineered from DF announcement code, also used when scrolling - +{ // Reverse-engineered from DF announcement code, also used when scrolling auto dims = getDwarfmodeViewDims(); int32_t w = dims.map_x2 - dims.map_x1 + 1; int32_t h = dims.map_y2 - dims.map_y1 + 1; @@ -2062,7 +1946,7 @@ void Gui::recenterViewscreen(int32_t x, int32_t y, int32_t z, df::report_zoom_ty } else // report_zoom_type::Item { - if (new_win_x > (x - 5)) + if (new_win_x > (x - 5)) // equivalent to: "while (new_win_x > x - 5) new_win_x -= 10;" new_win_x -= (new_win_x - (x - 5) - 1) / 10 * 10 + 10; if (new_win_y > (y - 5)) new_win_y -= (new_win_y - (y - 5) - 1) / 10 * 10 + 10; @@ -2072,32 +1956,28 @@ void Gui::recenterViewscreen(int32_t x, int32_t y, int32_t z, df::report_zoom_ty new_win_y += ((y + 5 - h) - new_win_y - 1) / 10 * 10 + 10; } - if (new_win_z != z) - ui_sidebar_menus->minimap.need_scan = true; new_win_z = z; } *df::global::window_x = clip_range(new_win_x, 0, (world->map.x_count - w)); *df::global::window_y = clip_range(new_win_y, 0, (world->map.y_count - h)); *df::global::window_z = clip_range(new_win_z, 0, (world->map.z_count - 1)); + + ui_sidebar_menus->minimap.need_render = true; + ui_sidebar_menus->minimap.need_scan = true; return; } void Gui::pauseRecenter(int32_t x, int32_t y, int32_t z, bool pause) -{ - // Reverse-engineered from DF announcement code - +{ // Reverse-engineered from DF announcement code if (*gamemode != game_mode::DWARF) return; resetDwarfmodeView(pause); + if (x != -30000) - { recenterViewscreen(x, y, z, report_zoom_type::Item); - ui_sidebar_menus->minimap.need_render = true; - ui_sidebar_menus->minimap.need_scan = true; - } if (init->input.pause_zoom_no_interface_ms > 0) { diff --git a/library/xml b/library/xml index a59495e8f..1dfe6c5ab 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit a59495e8f72115909772e6df20a7b9dec272f14c +Subproject commit 1dfe6c5ab9887507cdcdebdd9390352fe0bba2dd diff --git a/scripts b/scripts index 05d46b32a..741c84ada 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 05d46b32a3aff4f5f98534fdccfbf9ae88dd31a3 +Subproject commit 741c84ada2ec7fdd0083744afab294d9a1b6e370 diff --git a/test/library/gui/widgets.Label.lua b/test/library/gui/widgets.Label.lua new file mode 100644 index 000000000..49a75a235 --- /dev/null +++ b/test/library/gui/widgets.Label.lua @@ -0,0 +1,94 @@ +-- test -dhack/scripts/devel/tests -twidgets.Label + +local gui = require('gui') +local widgets = require('gui.widgets') + +local xtest = {} -- use to temporarily disable tests (change `function xtest.somename` to `function xxtest.somename`) +local wait = function(n) + delay(n or 30) -- enable for debugging the tests +end + +local fs = defclass(fs, gui.FramedScreen) +fs.ATTRS = { + frame_style = gui.GREY_LINE_FRAME, + frame_title = 'TestFramedScreen', + frame_width = 10, + frame_height = 10, + frame_inset = 0, + focus_path = 'test-framed-screen', +} + +function test.Label_correct_frame_body_with_scroll_icons() + local t = {} + for i = 1, 12 do + t[#t+1] = tostring(i) + t[#t+1] = NEWLINE + end + + function fs:init(args) + self:addviews{ + widgets.Label{ + view_id = 'text', + frame_inset = 0, + text = t, + --show_scroll_icons = 'right', + }, + } + end + + local o = fs{} + --o:show() + --wait() + 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.") + --o:dismiss() +end + +function test.Label_correct_frame_body_with_few_text_lines() + local t = {} + for i = 1, 10 do + t[#t+1] = tostring(i) + t[#t+1] = NEWLINE + end + + function fs:init(args) + self:addviews{ + widgets.Label{ + view_id = 'text', + frame_inset = 0, + text = t, + --show_scroll_icons = 'right', + }, + } + end + + local o = fs{} + --o:show() + --wait() + expect.eq(o.subviews.text.frame_body.width, 10, "Label's frame_body.x2 and .width should not change with show_scroll_icons = false.") + --o:dismiss() +end + +function test.Label_correct_frame_body_without_show_scroll_icons() + local t = {} + for i = 1, 12 do + t[#t+1] = tostring(i) + t[#t+1] = NEWLINE + end + + function fs:init(args) + self:addviews{ + widgets.Label{ + view_id = 'text', + frame_inset = 0, + text = t, + show_scroll_icons = false, + }, + } + end + + local o = fs{} + --o:show() + --wait() + expect.eq(o.subviews.text.frame_body.width, 10, "Label's frame_body.x2 and .width should not change with show_scroll_icons = false.") + --o:dismiss() +end diff --git a/test/library/test_util/mock.lua b/test/library/test_util/mock.lua index 32aed28e1..1031a496a 100644 --- a/test/library/test_util/mock.lua +++ b/test/library/test_util/mock.lua @@ -208,9 +208,44 @@ function test.func_call_return_value() end function test.func_call_return_multiple_values() - local f = mock.func(7,5,{imatable='snarfsnarf'}) + local f = mock.func(7, 5, {imatable='snarfsnarf'}) local a, b, c = f() expect.eq(7, a) expect.eq(5, b) expect.table_eq({imatable='snarfsnarf'}, c) end + +function test.observe_func() + -- basic end-to-end test for common cases; + -- most edge cases are covered by mock.func() tests + local counter = 0 + local function target() + counter = counter + 1 + return counter + end + local observer = mock.observe_func(target) + + expect.eq(observer(), 1) + expect.eq(counter, 1) + expect.eq(observer.call_count, 1) + expect.table_eq(observer.call_args, {{}}) + + expect.eq(observer('x', 'y'), 2) + expect.eq(counter, 2) + expect.eq(observer.call_count, 2) + expect.table_eq(observer.call_args, {{}, {'x', 'y'}}) +end + +function test.observe_func_error() + local function target() + error('asdf') + end + local observer = mock.observe_func(target) + + expect.error_match('asdf', function() + observer('x') + end) + -- make sure the call was still tracked + expect.eq(observer.call_count, 1) + expect.table_eq(observer.call_args, {{'x'}}) +end From e613085b0eec7ccc8b8a511b07e1dbf01ed81b25 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sat, 28 May 2022 12:39:49 -0700 Subject: [PATCH 18/32] remove whitespace --- library/modules/Gui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 0dd50414e..b931c22af 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -1688,7 +1688,7 @@ bool Gui::autoDFAnnouncement(df::report_init r, string message) { DEBUG(gui).print("Skipped announcement because world->allow_announcements is false:\n%s\n", message.c_str()); return false; - } + } else if (!is_valid_enum_item(r.type)) { WARN(gui).print("Invalid announcement type:\n%s\n", message.c_str()); From 249ed2888fcb582c91e4e21e57409a9b448de1b6 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sat, 28 May 2022 13:28:10 -0700 Subject: [PATCH 19/32] Fix scope issue --- library/modules/Gui.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index b931c22af..35c6ca8a5 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -1388,7 +1388,7 @@ namespace if (str[i] == 'r') // "&r" adds a blank line { - word_wrap(out, parsed, line_length, false, true); + word_wrap(out, parsed, line_length, false/*, true*/); out->push_back(" "); // DF adds a line with a space for some reason parsed.clear(); } @@ -1402,7 +1402,7 @@ namespace while (++i < str.length()); if (parsed.length()) - word_wrap(out, parsed, line_length, false, true); + word_wrap(out, parsed, line_length, false/*, true*/); return true; } @@ -1597,11 +1597,11 @@ namespace bool add_proper_report(df::unit *unit, bool is_sparring, int report_index) { if (is_sparring) - return addCombatReport(unit, unit_report_type::Sparring, report_index); + return Gui::addCombatReport(unit, unit_report_type::Sparring, report_index); else if (unit->job.current_job != NULL && unit->job.current_job->job_type == job_type::Hunt) - return addCombatReport(unit, unit_report_type::Hunting, report_index); + return Gui::addCombatReport(unit, unit_report_type::Hunting, report_index); else - return addCombatReport(unit, unit_report_type::Combat, report_index); + return Gui::addCombatReport(unit, unit_report_type::Combat, report_index); } } From 1c3ea000e1c26a88a3e27a5f5f951dc70bcb4295 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sat, 28 May 2022 15:56:49 -0700 Subject: [PATCH 20/32] Trim trailing whitespace --- docs/Lua API.rst | 6 +++--- library/include/modules/Gui.h | 4 ++-- library/modules/Gui.cpp | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 8ec347bef..efd963c03 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1020,7 +1020,7 @@ Fortress mode where ``df.report_zoom_type.Generic`` skips recentering and enforces valid view bounds (the same as x = -30000,) ``df.report_zoom_type.Item`` brings the position onscreen without centering, and ``df.report_zoom_type.Unit`` centers the screen on the position. Default zoom type is ``df.report_zoom_type.Item``. - + * ``dfhack.gui.revealInDwarfmodeMap(pos)`` Centers the view on the given position, which can be a ``df.coord`` instance @@ -1096,11 +1096,11 @@ Announcements Takes a ``df.report_init`` (see: `structure definition `_) and a string and processes them just like DF does. Can also be built from parameters instead of a ``report_init``. Setting ``is_sparring`` to *true* means the report will be added to sparring logs (if applicable) rather than hunting or combat. - + The announcement will not display if units are involved and the player can't see them (or hear, for adventure mode sound announcement types.) Text is parsed using ``&`` as an escape character, with ``&r`` adding a blank line (equivalent to ``\n \n``,) ``&&`` being just ``&``, and any other combination causing neither character to display. - + If you want a guaranteed announcement without parsing, use ``dfhack.gui.showAutoAnnouncement`` instead. Other diff --git a/library/include/modules/Gui.h b/library/include/modules/Gui.h index c7f857a1d..0d2f1c210 100644 --- a/library/include/modules/Gui.h +++ b/library/include/modules/Gui.h @@ -126,12 +126,12 @@ namespace DFHack // Show an announcement with effects determined by announcements.txt 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); - + // Process an announcement exactly like DF would, which might result in no announcement DFHACK_EXPORT bool autoDFAnnouncement(df::report_init r, std::string message); DFHACK_EXPORT bool autoDFAnnouncement(df::announcement_type type, df::coord pos, std::string message, int color = 7, bool bright = true, df::unit *unit1 = NULL, df::unit *unit2 = NULL, bool is_sparring = false); - + /* * Cursor and window coords */ diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 35c6ca8a5..90e6b41b1 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -1962,7 +1962,7 @@ void Gui::recenterViewscreen(int32_t x, int32_t y, int32_t z, df::report_zoom_ty *df::global::window_x = clip_range(new_win_x, 0, (world->map.x_count - w)); *df::global::window_y = clip_range(new_win_y, 0, (world->map.y_count - h)); *df::global::window_z = clip_range(new_win_z, 0, (world->map.z_count - 1)); - + ui_sidebar_menus->minimap.need_render = true; ui_sidebar_menus->minimap.need_scan = true; @@ -1975,7 +1975,7 @@ void Gui::pauseRecenter(int32_t x, int32_t y, int32_t z, bool pause) return; resetDwarfmodeView(pause); - + if (x != -30000) recenterViewscreen(x, y, z, report_zoom_type::Item); From 0ff0d272b5936584b980642c44d44a3f1273d59e Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Mon, 30 May 2022 13:51:24 -0700 Subject: [PATCH 21/32] use static instead of anon namespace; suggested changes --- library/include/modules/Gui.h | 2 +- library/modules/Gui.cpp | 151 ++++++++++++++++------------------ 2 files changed, 73 insertions(+), 80 deletions(-) diff --git a/library/include/modules/Gui.h b/library/include/modules/Gui.h index 0d2f1c210..bfe165179 100644 --- a/library/include/modules/Gui.h +++ b/library/include/modules/Gui.h @@ -140,7 +140,7 @@ namespace DFHack // Recenter the viewscreen, based on DF code for announcements and scrolling DFHACK_EXPORT void pauseRecenter(int32_t x, int32_t y, int32_t z, bool pause); - DFHACK_EXPORT inline void pauseRecenter(df::coord pos, bool pause) { return pauseRecenter(pos.x, pos.y, pos.z, pause); } + DFHACK_EXPORT inline void pauseRecenter(df::coord pos, bool pause) { pauseRecenter(pos.x, pos.y, pos.z, pause); } DFHACK_EXPORT void recenterViewscreen(int32_t x, int32_t y, int32_t z, df::report_zoom_type zoom = df::enums::report_zoom_type::Item); DFHACK_EXPORT inline void recenterViewscreen(df::coord pos, df::report_zoom_type zoom = df::enums::report_zoom_type::Item) { recenterViewscreen(pos.x, pos.y, pos.z, zoom); }; DFHACK_EXPORT inline void recenterViewscreen(df::report_zoom_type zoom = df::enums::report_zoom_type::Item) { recenterViewscreen(getCursorPos(), zoom); }; diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 90e6b41b1..65a9fbcb5 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -1368,99 +1368,94 @@ DFHACK_EXPORT void Gui::writeToGamelog(std::string message) fseed.close(); } -namespace -{ // Utility functions for reports - bool parseReportString(std::vector *out, const std::string &str, size_t line_length = 73) - { // parse a string into output strings like DF does for reports - if (str.empty() || line_length == 0) - return false; +// Utility functions for reports +static bool parseReportString(std::vector *out, const std::string &str, size_t line_length = 73) +{ // parse a string into output strings like DF does for reports + if (str.empty() || line_length == 0) + return false; - string parsed; - size_t i = 0; + string parsed; + size_t i = 0; - do + do + { + if (str[i] == '&') // escape character { - if (str[i] == '&') // escape character - { - i++; // ignore the '&' itself - if (i >= str.length()) - break; + i++; // ignore the '&' itself + if (i >= str.length()) + break; - if (str[i] == 'r') // "&r" adds a blank line - { - word_wrap(out, parsed, line_length, false/*, true*/); - out->push_back(" "); // DF adds a line with a space for some reason - parsed.clear(); - } - else if (str[i] == '&') // "&&" is '&' - parsed.push_back('&'); - // else next char is ignored + if (str[i] == 'r') // "&r" adds a blank line + { + word_wrap(out, parsed, line_length/*, WSMODE_TRIM_LEADING*/); + out->push_back(" "); // DF adds a line with a space for some reason + parsed.clear(); } - else - parsed.push_back(str[i]); + else if (str[i] == '&') // "&&" is '&' + parsed.push_back('&'); + // else next char is ignored } - while (++i < str.length()); + else + parsed.push_back(str[i]); + } + while (++i < str.length()); - if (parsed.length()) - word_wrap(out, parsed, line_length, false/*, true*/); + if (parsed.length()) + word_wrap(out, parsed, line_length/*, WSMODE_TRIM_LEADING*/); - return true; - } + return true; +} - bool recent_report(df::unit *unit, df::unit_report_type slot) +static bool recent_report(df::unit *unit, df::unit_report_type slot) +{ + return unit && !unit->reports.log[slot].empty() && + *df::global::cur_year == unit->reports.last_year[slot] && + (*df::global::cur_year_tick - unit->reports.last_year_tick[slot]) <= 500; +} + +static bool recent_report_any(df::unit *unit) +{ + FOR_ENUM_ITEMS(unit_report_type, slot) { - if (unit && !unit->reports.log[slot].empty() && - *df::global::cur_year == unit->reports.last_year[slot] && - (*df::global::cur_year_tick - unit->reports.last_year_tick[slot]) <= 500) - { + if (recent_report(unit, slot)) return true; - } - return false; } + return false; +} - bool recent_report_any(df::unit *unit) +static void delete_old_reports() +{ + auto &reports = world->status.reports; + while (reports.size() > 3000) { - FOR_ENUM_ITEMS(unit_report_type, slot) + if (reports[0] != NULL) { - if (recent_report(unit, slot)) - return true; + if (reports[0]->flags.bits.announcement) + erase_from_vector(world->status.announcements, &df::report::id, reports[0]->id); + delete reports[0]; } - return false; + reports.erase(reports.begin()); } +} - void delete_old_reports() +static int32_t check_repeat_report(vector &results) +{ // returns the new repeat count, else 0 + if (*gamemode == game_mode::DWARF && !results.empty() && world->status.reports.size() >= results.size()) { auto &reports = world->status.reports; - while (reports.size() > 3000) - { - if (reports[0] != NULL) - { - if (reports[0]->flags.bits.announcement) - erase_from_vector(world->status.announcements, &df::report::id, reports[0]->id); - delete reports[0]; - } - reports.erase(reports.begin()); - } - } + size_t base = reports.size() - results.size(); // index where a repeat would start + size_t offset = 0; + while (reports[base + offset]->text == results[offset] && ++offset < results.size()); // match each report - int32_t check_repeat_report(vector &results) - { // returns the new repeat count, else 0 - if (*gamemode == game_mode::DWARF && !results.empty() && world->status.reports.size() >= results.size()) + if (offset == results.size()) // all lines matched { - auto &reports = world->status.reports; - size_t base = reports.size() - results.size(); // index where a repeat would start - size_t offset = 0; - while (reports[base + offset]->text == results[offset] && ++offset < results.size()); // match each report - - if (offset == results.size()) // all lines matched - { - reports[base]->duration = 100; - return ++(reports[base]->repeat_count); - } + reports[base]->duration = 100; + return ++(reports[base]->repeat_count); } - return 0; } + return 0; } +// End of utility functions for reports DFHACK_EXPORT int Gui::makeAnnouncement(df::announcement_type type, df::announcement_flags flags, df::coord pos, std::string message, int color, bool bright) { @@ -1592,17 +1587,15 @@ bool Gui::addCombatReport(df::unit *unit, df::unit_report_type slot, int report_ return true; } -namespace -{ // An additional utility function for reports - bool add_proper_report(df::unit *unit, bool is_sparring, int report_index) - { - if (is_sparring) - return Gui::addCombatReport(unit, unit_report_type::Sparring, report_index); - else if (unit->job.current_job != NULL && unit->job.current_job->job_type == job_type::Hunt) - return Gui::addCombatReport(unit, unit_report_type::Hunting, report_index); - else - return Gui::addCombatReport(unit, unit_report_type::Combat, report_index); - } +// An additional utility function for reports +static bool add_proper_report(df::unit *unit, bool is_sparring, int report_index) +{ + if (is_sparring) + return Gui::addCombatReport(unit, unit_report_type::Sparring, report_index); + else if (unit->job.current_job != NULL && unit->job.current_job->job_type == job_type::Hunt) + return Gui::addCombatReport(unit, unit_report_type::Hunting, report_index); + else + return Gui::addCombatReport(unit, unit_report_type::Combat, report_index); } bool Gui::addCombatReportAuto(df::unit *unit, df::announcement_flags mode, int report_index) From 574728ac5ca1b1edff55f36defe180f77979ceec Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Mon, 30 May 2022 15:04:43 -0700 Subject: [PATCH 22/32] Move add_proper_report up with other utility fns --- library/modules/Gui.cpp | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 65a9fbcb5..16fd0057a 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -1455,6 +1455,16 @@ static int32_t check_repeat_report(vector &results) } return 0; } + +static bool add_proper_report(df::unit *unit, bool is_sparring, int report_index) +{ + if (is_sparring) + return Gui::addCombatReport(unit, unit_report_type::Sparring, report_index); + else if (unit->job.current_job != NULL && unit->job.current_job->job_type == job_type::Hunt) + return Gui::addCombatReport(unit, unit_report_type::Hunting, report_index); + else + return Gui::addCombatReport(unit, unit_report_type::Combat, report_index); +} // End of utility functions for reports DFHACK_EXPORT int Gui::makeAnnouncement(df::announcement_type type, df::announcement_flags flags, df::coord pos, std::string message, int color, bool bright) @@ -1587,17 +1597,6 @@ bool Gui::addCombatReport(df::unit *unit, df::unit_report_type slot, int report_ return true; } -// An additional utility function for reports -static bool add_proper_report(df::unit *unit, bool is_sparring, int report_index) -{ - if (is_sparring) - return Gui::addCombatReport(unit, unit_report_type::Sparring, report_index); - else if (unit->job.current_job != NULL && unit->job.current_job->job_type == job_type::Hunt) - return Gui::addCombatReport(unit, unit_report_type::Hunting, report_index); - else - return Gui::addCombatReport(unit, unit_report_type::Combat, report_index); -} - bool Gui::addCombatReportAuto(df::unit *unit, df::announcement_flags mode, int report_index) { using df::global::world; From b0b601cf0fe55e6a4d93ee2f18db4392194e50dd Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sat, 4 Jun 2022 11:59:04 -0700 Subject: [PATCH 23/32] Remove recenterViewscreen, update revealInDwarfmodeMap --- docs/Lua API.rst | 24 +++---- docs/changelog.txt | 4 +- library/LuaApi.cpp | 92 +++++++++++++----------- library/include/modules/Gui.h | 12 ++-- library/modules/Gui.cpp | 129 +++++++++++++--------------------- 5 files changed, 116 insertions(+), 145 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index efd963c03..803b84768 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1007,31 +1007,23 @@ Fortress mode * ``dfhack.gui.pauseRecenter(pos[,pause])`` ``dfhack.gui.pauseRecenter(x,y,z[,pause])`` - Same as ``resetDwarfmodeView``, but also recenter if ``x`` isn't ``-30000``. Respects + Same as ``resetDwarfmodeView``, but also recenter if position is valid. If ``pause`` is false, skip pausing. Respects ``RECENTER_INTERFACE_SHUTDOWN_MS`` in DF's ``init.txt`` (the delay before input is recognized when a recenter occurs.) -* ``dfhack.gui.recenterViewscreen(pos[,zoom])`` - ``dfhack.gui.recenterViewscreen(x,y,z[,zoom])`` - ``dfhack.gui.recenterViewscreen([zoom])`` +* ``dfhack.gui.revealInDwarfmodeMap(pos[,center])`` + ``dfhack.gui.revealInDwarfmodeMap(x,y,z[,center])`` - Recenter the view on a position using a specific zoom type. If no position is given, - recenter on ``df.global.cursor``. Zoom types are ``df.report_zoom_type`` - (see: `enum definition `_), - where ``df.report_zoom_type.Generic`` skips recentering and enforces valid view bounds (the same as x = -30000,) - ``df.report_zoom_type.Item`` brings the position onscreen without centering, and - ``df.report_zoom_type.Unit`` centers the screen on the position. Default zoom type is ``df.report_zoom_type.Item``. - -* ``dfhack.gui.revealInDwarfmodeMap(pos)`` - - Centers the view on the given position, which can be a ``df.coord`` instance - or a table assignable to a ``df.coord`` (see `lua-api-table-assignment`), + Centers the view on the given coordinates. If ``center`` is true, make sure the + position is in the exact center of the view, else just bring it on screen. + + ``pos`` can be a ``df.coord`` instance or a table assignable to a ``df.coord`` (see `lua-api-table-assignment`), e.g.:: {x = 5, y = 7, z = 11} getSelectedUnit().pos copyall(df.global.cursor) - Returns false if unsuccessful. + If the position is invalid, the function will simply ensure the current window position is clamped between valid values. * ``dfhack.gui.refreshSidebar()`` diff --git a/docs/changelog.txt b/docs/changelog.txt index 34b89d93e..fc184a35e 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -45,9 +45,11 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `dfhack-examples-guide`: refine food preparation orders and fix conditions for making jugs and pots in the ``basic`` manager orders ## Documentation +- ``dfhack.gui.revealInDwarfmodeMap``: document ``center`` bool for lua API ## API -- add functions reverse-engineered from announcement code: ``Gui::autoDFAnnouncement``, ``Gui::pauseRecenter``, ``Gui::recenterViewscreen`` +- add functions reverse-engineered from announcement code: ``Gui::autoDFAnnouncement``, ``Gui::pauseRecenter`` +- ``Gui::revealInDwarfmodeMap``: Now enforce valid view bounds when pos invalid, add variant accepting x, y, z ## Lua - ``widgets.HotkeyLabel``: the ``key_sep`` string is now configurable diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 5881e1b1f..b4e4fe3cd 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1457,26 +1457,6 @@ static const LuaWrapper::FunctionReg dfhack_module[] = { /***** Gui module *****/ -static int gui_getDwarfmodeViewDims(lua_State *state) -{ - auto dims = Gui::getDwarfmodeViewDims(); - lua_newtable(state); - Lua::TableInsert(state, "map_x1", dims.map_x1); - Lua::TableInsert(state, "map_x2", dims.map_x2); - Lua::TableInsert(state, "menu_x1", dims.menu_x1); - Lua::TableInsert(state, "menu_x2", dims.menu_x2); - Lua::TableInsert(state, "area_x1", dims.area_x1); - Lua::TableInsert(state, "area_x2", dims.area_x2); - Lua::TableInsert(state, "y1", dims.y1); - Lua::TableInsert(state, "y2", dims.y2); - Lua::TableInsert(state, "map_y1", dims.map_y1); - Lua::TableInsert(state, "map_y2", dims.map_y2); - Lua::TableInsert(state, "menu_on", dims.menu_on); - Lua::TableInsert(state, "area_on", dims.area_on); - Lua::TableInsert(state, "menu_forced", dims.menu_forced); - return 1; -} - static const LuaWrapper::FunctionReg dfhack_gui_module[] = { WRAPM(Gui, getCurViewscreen), WRAPM(Gui, getFocusString), @@ -1500,7 +1480,6 @@ static const LuaWrapper::FunctionReg dfhack_gui_module[] = { WRAPM(Gui, showPopupAnnouncement), WRAPM(Gui, showAutoAnnouncement), WRAPM(Gui, resetDwarfmodeView), - WRAPM(Gui, revealInDwarfmodeMap), WRAPM(Gui, refreshSidebar), WRAPM(Gui, inRenameBuilding), WRAPM(Gui, getDepthAt), @@ -1520,7 +1499,7 @@ static int gui_autoDFAnnouncement(lua_State *state) else { df::coord pos; - int color = 0; //initialize these to prevent warning + int color = 0; // initialize these to prevent warning bool bright = false, is_sparring = false; df::unit *unit1 = NULL, *unit2 = NULL; @@ -1572,54 +1551,85 @@ static int gui_autoDFAnnouncement(lua_State *state) return 1; } +static int gui_getDwarfmodeViewDims(lua_State *state) +{ + auto dims = Gui::getDwarfmodeViewDims(); + lua_newtable(state); + Lua::TableInsert(state, "map_x1", dims.map_x1); + Lua::TableInsert(state, "map_x2", dims.map_x2); + Lua::TableInsert(state, "menu_x1", dims.menu_x1); + Lua::TableInsert(state, "menu_x2", dims.menu_x2); + Lua::TableInsert(state, "area_x1", dims.area_x1); + Lua::TableInsert(state, "area_x2", dims.area_x2); + Lua::TableInsert(state, "y1", dims.y1); + Lua::TableInsert(state, "y2", dims.y2); + Lua::TableInsert(state, "map_y1", dims.map_y1); + Lua::TableInsert(state, "map_y2", dims.map_y2); + Lua::TableInsert(state, "menu_on", dims.menu_on); + Lua::TableInsert(state, "area_on", dims.area_on); + Lua::TableInsert(state, "menu_forced", dims.menu_forced); + return 1; +} + static int gui_pauseRecenter(lua_State *state) { - if (lua_gettop(state) == 2) + bool rv; + df::coord p; + + switch (lua_gettop(state)) { - df::coord p; - Lua::CheckDFAssign(state, &p, 1); - Gui::pauseRecenter(p, lua_toboolean(state, 2)); + default: + case 4: + rv = Gui::pauseRecenter(CheckCoordXYZ(state, 1, false), lua_toboolean(state, 4)); + break; + case 3: + rv = Gui::pauseRecenter(CheckCoordXYZ(state, 1, false)); + break; + case 2: + Lua::CheckDFAssign(state, &p, 1); + rv = Gui::pauseRecenter(p, lua_toboolean(state, 2)); + break; + case 1: + Lua::CheckDFAssign(state, &p, 1); + rv = Gui::pauseRecenter(p); } - else - Gui::pauseRecenter(CheckCoordXYZ(state, 1, false), lua_toboolean(state, 4)); + lua_pushboolean(state, rv); return 1; } -static int gui_recenterViewscreen(lua_State *state) +static int gui_revealInDwarfmodeMap(lua_State *state) { + bool rv; df::coord p; + switch (lua_gettop(state)) { default: case 4: - Gui::recenterViewscreen(CheckCoordXYZ(state, 1, false), (df::report_zoom_type)lua_tointeger(state, 4)); + rv = Gui::revealInDwarfmodeMap(CheckCoordXYZ(state, 1, false), lua_toboolean(state, 4)); break; case 3: - Gui::recenterViewscreen(CheckCoordXYZ(state, 1, false)); + rv = Gui::revealInDwarfmodeMap(CheckCoordXYZ(state, 1, false)); break; case 2: Lua::CheckDFAssign(state, &p, 1); - Gui::recenterViewscreen(p, (df::report_zoom_type)lua_tointeger(state, 2)); + rv = Gui::revealInDwarfmodeMap(p, lua_toboolean(state, 2)); break; case 1: - if (lua_type(state, 1) == LUA_TNUMBER) - Gui::recenterViewscreen((df::report_zoom_type)lua_tointeger(state, 1)); - else - Gui::recenterViewscreen(CheckCoordXYZ(state, 1, true)); - break; - case 0: - Gui::recenterViewscreen(); + Lua::CheckDFAssign(state, &p, 1); + rv = Gui::revealInDwarfmodeMap(p); } + lua_pushboolean(state, rv); return 1; } static const luaL_Reg dfhack_gui_funcs[] = { { "autoDFAnnouncement", gui_autoDFAnnouncement }, - { "pauseRecenter", gui_pauseRecenter }, - { "recenterViewscreen", gui_recenterViewscreen }, { "getDwarfmodeViewDims", gui_getDwarfmodeViewDims }, + { "pauseRecenter", gui_pauseRecenter }, + { "revealInDwarfmodeMap", gui_revealInDwarfmodeMap }, { NULL, NULL } }; diff --git a/library/include/modules/Gui.h b/library/include/modules/Gui.h index bfe165179..c44d33f4e 100644 --- a/library/include/modules/Gui.h +++ b/library/include/modules/Gui.h @@ -138,13 +138,6 @@ namespace DFHack DFHACK_EXPORT df::coord getViewportPos(); DFHACK_EXPORT df::coord getCursorPos(); - // Recenter the viewscreen, based on DF code for announcements and scrolling - DFHACK_EXPORT void pauseRecenter(int32_t x, int32_t y, int32_t z, bool pause); - DFHACK_EXPORT inline void pauseRecenter(df::coord pos, bool pause) { pauseRecenter(pos.x, pos.y, pos.z, pause); } - DFHACK_EXPORT void recenterViewscreen(int32_t x, int32_t y, int32_t z, df::report_zoom_type zoom = df::enums::report_zoom_type::Item); - DFHACK_EXPORT inline void recenterViewscreen(df::coord pos, df::report_zoom_type zoom = df::enums::report_zoom_type::Item) { recenterViewscreen(pos.x, pos.y, pos.z, zoom); }; - DFHACK_EXPORT inline void recenterViewscreen(df::report_zoom_type zoom = df::enums::report_zoom_type::Item) { recenterViewscreen(getCursorPos(), zoom); }; - static const int AREA_MAP_WIDTH = 23; static const int MENU_WIDTH = 30; @@ -161,7 +154,10 @@ namespace DFHack DFHACK_EXPORT DwarfmodeDims getDwarfmodeViewDims(); DFHACK_EXPORT void resetDwarfmodeView(bool pause = false); - DFHACK_EXPORT bool revealInDwarfmodeMap(df::coord pos, bool center = false); + DFHACK_EXPORT bool revealInDwarfmodeMap(int32_t x, int32_t y, int32_t z, bool center = false); + DFHACK_EXPORT inline bool revealInDwarfmodeMap(df::coord pos, bool center = false) { return revealInDwarfmodeMap(pos.x, pos.y, pos.z, center); }; + DFHACK_EXPORT bool pauseRecenter(int32_t x, int32_t y, int32_t z, bool pause = true); + DFHACK_EXPORT inline bool pauseRecenter(df::coord pos, bool pause = true) { return pauseRecenter(pos.x, pos.y, pos.z, pause); }; DFHACK_EXPORT bool refreshSidebar(); DFHACK_EXPORT bool inRenameBuilding(); diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 16fd0057a..fa4535238 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -1387,7 +1387,7 @@ static bool parseReportString(std::vector *out, const std::string & if (str[i] == 'r') // "&r" adds a blank line { - word_wrap(out, parsed, line_length/*, WSMODE_TRIM_LEADING*/); + word_wrap(out, parsed, line_length, WSMODE_TRIM_LEADING); out->push_back(" "); // DF adds a line with a space for some reason parsed.clear(); } @@ -1401,7 +1401,7 @@ static bool parseReportString(std::vector *out, const std::string & while (++i < str.length()); if (parsed.length()) - word_wrap(out, parsed, line_length/*, WSMODE_TRIM_LEADING*/); + word_wrap(out, parsed, line_length, WSMODE_TRIM_LEADING); return true; } @@ -1921,65 +1921,6 @@ df::coord Gui::getCursorPos() return df::coord(cursor->x, cursor->y, cursor->z); } -void Gui::recenterViewscreen(int32_t x, int32_t y, int32_t z, df::report_zoom_type zoom) -{ // Reverse-engineered from DF announcement code, also used when scrolling - auto dims = getDwarfmodeViewDims(); - int32_t w = dims.map_x2 - dims.map_x1 + 1; - int32_t h = dims.map_y2 - dims.map_y1 + 1; - int32_t new_win_x, new_win_y, new_win_z; - getViewCoords(new_win_x, new_win_y, new_win_z); - - if (zoom != report_zoom_type::Generic && x != -30000) - { - if (zoom == report_zoom_type::Unit) - { - new_win_x = x - w / 2; - new_win_y = y - h / 2; - } - else // report_zoom_type::Item - { - if (new_win_x > (x - 5)) // equivalent to: "while (new_win_x > x - 5) new_win_x -= 10;" - new_win_x -= (new_win_x - (x - 5) - 1) / 10 * 10 + 10; - if (new_win_y > (y - 5)) - new_win_y -= (new_win_y - (y - 5) - 1) / 10 * 10 + 10; - if (new_win_x < (x + 5 - w)) - new_win_x += ((x + 5 - w) - new_win_x - 1) / 10 * 10 + 10; - if (new_win_y < (y + 5 - h)) - new_win_y += ((y + 5 - h) - new_win_y - 1) / 10 * 10 + 10; - } - - new_win_z = z; - } - - *df::global::window_x = clip_range(new_win_x, 0, (world->map.x_count - w)); - *df::global::window_y = clip_range(new_win_y, 0, (world->map.y_count - h)); - *df::global::window_z = clip_range(new_win_z, 0, (world->map.z_count - 1)); - - ui_sidebar_menus->minimap.need_render = true; - ui_sidebar_menus->minimap.need_scan = true; - - return; -} - -void Gui::pauseRecenter(int32_t x, int32_t y, int32_t z, bool pause) -{ // Reverse-engineered from DF announcement code - if (*gamemode != game_mode::DWARF) - return; - - resetDwarfmodeView(pause); - - if (x != -30000) - recenterViewscreen(x, y, z, report_zoom_type::Item); - - if (init->input.pause_zoom_no_interface_ms > 0) - { - gview->shutdown_interface_tickcount = Core::getInstance().p->getTickCount(); - gview->shutdown_interface_for_ms = init->input.pause_zoom_no_interface_ms; - } - - return; -} - Gui::DwarfmodeDims getDwarfmodeViewDims_default() { Gui::DwarfmodeDims dims; @@ -2059,38 +2000,68 @@ void Gui::resetDwarfmodeView(bool pause) *df::global::pause_state = true; } -bool Gui::revealInDwarfmodeMap(df::coord pos, bool center) -{ +bool Gui::revealInDwarfmodeMap(int32_t x, int32_t y, int32_t z, bool center) +{ // Reverse-engineered from DF announcement and scrolling code using df::global::window_x; using df::global::window_y; using df::global::window_z; if (!window_x || !window_y || !window_z || !world) return false; - if (!Maps::isValidTilePos(pos)) - return false; auto dims = getDwarfmodeViewDims(); - int w = dims.map_x2 - dims.map_x1 + 1; - int h = dims.map_y2 - dims.map_y1 + 1; - - *window_z = pos.z; + int32_t w = dims.map_x2 - dims.map_x1 + 1; + int32_t h = dims.map_y2 - dims.map_y1 + 1; + int32_t new_win_x, new_win_y, new_win_z; + getViewCoords(new_win_x, new_win_y, new_win_z); - if (center) + if (Maps::isValidTilePos(x, y, z)) { - *window_x = pos.x - w/2; - *window_y = pos.y - h/2; + if (center) + { + new_win_x = x - w / 2; + new_win_y = y - h / 2; + } + else // just bring it on screen + { + if (new_win_x > (x - 5)) // equivalent to: "while (new_win_x > x - 5) new_win_x -= 10;" + new_win_x -= (new_win_x - (x - 5) - 1) / 10 * 10 + 10; + if (new_win_y > (y - 5)) + new_win_y -= (new_win_y - (y - 5) - 1) / 10 * 10 + 10; + if (new_win_x < (x + 5 - w)) + new_win_x += ((x + 5 - w) - new_win_x - 1) / 10 * 10 + 10; + if (new_win_y < (y + 5 - h)) + new_win_y += ((y + 5 - h) - new_win_y - 1) / 10 * 10 + 10; + } + + new_win_z = z; } - else + + *window_x = clip_range(new_win_x, 0, (world->map.x_count - w)); + *window_y = clip_range(new_win_y, 0, (world->map.y_count - h)); + *window_z = clip_range(new_win_z, 0, (world->map.z_count - 1)); + ui_sidebar_menus->minimap.need_render = true; + ui_sidebar_menus->minimap.need_scan = true; + + return true; +} + +bool Gui::pauseRecenter(int32_t x, int32_t y, int32_t z, bool pause) +{ // Reverse-engineered from DF announcement code + if (*gamemode != game_mode::DWARF) + return false; + + resetDwarfmodeView(pause); + + if (Maps::isValidTilePos(x, y, z)) + revealInDwarfmodeMap(x, y, z, false); + + if (init->input.pause_zoom_no_interface_ms > 0) { - while (*window_x + w < pos.x+5) *window_x += 10; - while (*window_y + h < pos.y+5) *window_y += 10; - while (*window_x + 5 > pos.x) *window_x -= 10; - while (*window_y + 5 > pos.y) *window_y -= 10; + gview->shutdown_interface_tickcount = Core::getInstance().p->getTickCount(); + gview->shutdown_interface_for_ms = init->input.pause_zoom_no_interface_ms; } - *window_x = std::max(0, std::min(*window_x, world->map.x_count-w)); - *window_y = std::max(0, std::min(*window_y, world->map.y_count-h)); return true; } From f993c23d755c473535beffaea0634b1d70fb81dd Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sat, 4 Jun 2022 12:22:19 -0700 Subject: [PATCH 24/32] fix whitespace --- docs/Lua API.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index c292eb560..211636abb 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1015,7 +1015,7 @@ Fortress mode Centers the view on the given coordinates. If ``center`` is true, make sure the position is in the exact center of the view, else just bring it on screen. - + ``pos`` can be a ``df.coord`` instance or a table assignable to a ``df.coord`` (see `lua-api-table-assignment`), e.g.:: From 16b5cade001d703e0cb714b64f97622cf0478fad Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sat, 4 Jun 2022 15:23:57 -0700 Subject: [PATCH 25/32] Add constants, update old fns Add MAX_REPORTS_SIZE, RECENT_REPORT_TICKS Remove redundant "using df::global::world" inside fns Update `makeAnnouncement`: Use `word_wrap`, `pauseRecenter`, and utility fn `delete_old_reports` Handle repeat announcements Insert sorted into ``world->status.announcements`` Update `addCombatReportAuto`: Use utility fn `recent_report` Update `showPopupAnnouncement`: Delete old popups at end of fn Update `getDwarfmodeViewDims_default`: Check for ui_sidebar_mode::Default and ArenaWeather --- library/modules/Gui.cpp | 130 +++++++++++++++++++--------------------- 1 file changed, 63 insertions(+), 67 deletions(-) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index fa4535238..1959c54a1 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -113,6 +113,9 @@ using namespace DFHack; #include "df/viewscreen_workshop_profilest.h" #include "df/world.h" +const size_t MAX_REPORTS_SIZE = 3000; +const int32_t RECENT_REPORT_TICKS = 500; + namespace DFHack { DBG_DECLARE(core, gui, DebugCategory::LINFO); @@ -1410,7 +1413,7 @@ static bool recent_report(df::unit *unit, df::unit_report_type slot) { return unit && !unit->reports.log[slot].empty() && *df::global::cur_year == unit->reports.last_year[slot] && - (*df::global::cur_year_tick - unit->reports.last_year_tick[slot]) <= 500; + (*df::global::cur_year_tick - unit->reports.last_year_tick[slot]) <= RECENT_REPORT_TICKS; } static bool recent_report_any(df::unit *unit) @@ -1426,7 +1429,7 @@ static bool recent_report_any(df::unit *unit) static void delete_old_reports() { auto &reports = world->status.reports; - while (reports.size() > 3000) + while (reports.size() > MAX_REPORTS_SIZE) { if (reports[0] != NULL) { @@ -1457,7 +1460,7 @@ static int32_t check_repeat_report(vector &results) } static bool add_proper_report(df::unit *unit, bool is_sparring, int report_index) -{ +{ // add report to proper category based on is_sparring and unit current job if (is_sparring) return Gui::addCombatReport(unit, unit_report_type::Sparring, report_index); else if (unit->job.current_job != NULL && unit->job.current_job->job_type == job_type::Hunt) @@ -1469,96 +1472,84 @@ static bool add_proper_report(df::unit *unit, bool is_sparring, int report_index DFHACK_EXPORT int Gui::makeAnnouncement(df::announcement_type type, df::announcement_flags flags, df::coord pos, std::string message, int color, bool bright) { - using df::global::world; - using df::global::cur_year; - using df::global::cur_year_tick; - - if (message.empty()) - return -1; - - int year = 0, year_time = 0; - - if (cur_year && cur_year_tick) + if (gamemode == NULL || cur_year == NULL || cur_year_tick == NULL) { - year = *cur_year; - year_time = *cur_year_tick; + return -1; } - else if (!world->status.reports.empty()) + else if (message.empty()) { - // Fallback: copy from the last report - df::report *last = world->status.reports.back(); - year = last->year; - year_time = last->time; + Core::printerr("Empty announcement %u\n", r.type); // DF would print this to errorlog.txt + return -1; } // Apply the requested effects - writeToGamelog(message); - if (flags.bits.DO_MEGA || flags.bits.PAUSE || flags.bits.RECENTER) - { - resetDwarfmodeView(flags.bits.DO_MEGA || flags.bits.PAUSE); + if (flags.bits.PAUSE || flags.bits.RECENTER) + pauseRecenter((flags.bits.RECENTER ? pos : df::coord()), flags.bits.PAUSE); - if (flags.bits.RECENTER && pos.isValid()) - revealInDwarfmodeMap(pos, true); + bool adv_unconscious = (*gamemode == game_mode::ADVENTURE && !world->units.active.empty() && world->units.active[0]->counters.unconscious > 0); - if (flags.bits.DO_MEGA) - showPopupAnnouncement(message, color, bright); - } + if (flags.bits.DO_MEGA && !adv_unconscious) + showPopupAnnouncement(message, color, bright); - bool display = false; + vector results; + word_wrap(&results, message, init->display.grid_x - 7); - if (gamemode == NULL) - display = flags.bits.A_DISPLAY || flags.bits.D_DISPLAY; - else if (*gamemode == game_mode::ADVENTURE) - display = flags.bits.A_DISPLAY; - else - display = flags.bits.D_DISPLAY; + // Check for repeat report + int32_t repeat_count = check_repeat_report(results); // Does nothing outside dwarf mode + if (repeat_count > 0) + { + if (flags.bits.D_DISPLAY) + { + world->status.display_timer = 2000; + Gui::writeToGamelog("x" + to_string(repeat_count + 1)); + } + return -1; + } + + // Not a repeat, write the message to gamelog.txt + writeToGamelog(message); // Generate the report objects int report_idx = world->status.reports.size(); bool continued = false; + bool display = ((*gamemode == game_mode::ADVENTURE && a_flags.bits.A_DISPLAY) || (*gamemode == game_mode::DWARF && a_flags.bits.D_DISPLAY)); - while (!message.empty()) + for (size_t i = 0; i < results.size(); i++) { - df::report *new_rep = new df::report(); - + auto new_rep = new df::report(); new_rep->type = type; new_rep->pos = pos; new_rep->color = color; new_rep->bright = bright; - new_rep->year = year; - new_rep->time = year_time; + new_rep->year = *df::global::cur_year; + new_rep->time = *df::global::cur_year_tick; new_rep->flags.bits.continuation = continued; - - int size = std::min(message.size(), (size_t)73); - new_rep->text = message.substr(0, size); - message = message.substr(size); - continued = true; - // Add the object to the lists + new_rep->text = results[i]; new_rep->id = world->status.next_report_id++; - world->status.reports.push_back(new_rep); + if (adv_unconscious) + new_rep->flags.bits.unconscious = true; + if (display) { + insert_into_vector(world->status.announcements, &df::report::id, new_rep); new_rep->flags.bits.announcement = true; - world->status.announcements.push_back(new_rep); world->status.display_timer = 2000; } } + delete_old_reports(); return report_idx; } - bool Gui::addCombatReport(df::unit *unit, df::unit_report_type slot, int report_index) { - using df::global::world; - CHECK_INVALID_ARGUMENT(is_valid_enum_item(slot)); auto &vec = world->status.reports; @@ -1599,8 +1590,6 @@ bool Gui::addCombatReport(df::unit *unit, df::unit_report_type slot, int report_ bool Gui::addCombatReportAuto(df::unit *unit, df::announcement_flags mode, int report_index) { - using df::global::world; - auto &vec = world->status.reports; auto report = vector_get(vec, report_index); @@ -1616,12 +1605,8 @@ bool Gui::addCombatReportAuto(df::unit *unit, df::announcement_flags mode, int r { FOR_ENUM_ITEMS(unit_report_type, slot) { - if (!unit->reports.log[slot].empty() && - unit->reports.last_year[slot] == report->year && - (report->time - unit->reports.last_year_tick[slot]) <= 500) - { + if (recent_report(unit, slot)) ok |= addCombatReport(unit, slot, report_index); - } } } @@ -1647,13 +1632,20 @@ void Gui::showZoomAnnouncement( void Gui::showPopupAnnouncement(std::string message, int color, bool bright) { - using df::global::world; - df::popup_message *popup = new df::popup_message(); popup->text = message; popup->color = color; popup->bright = bright; - world->status.popups.push_back(popup); + + auto &popups = world->status.popups; + popups.push_back(popup); + + while (popups.size() > MAX_REPORTS_SIZE) + { // Delete old popups + if (popups[0] != NULL) + delete popups[0]; + popups.erase(popups.begin()); + } } void Gui::showAutoAnnouncement( @@ -1750,7 +1742,9 @@ bool Gui::autoDFAnnouncement(df::report_init r, string message) if (a_flags.bits.PAUSE || a_flags.bits.RECENTER) pauseRecenter((a_flags.bits.RECENTER ? r.pos : df::coord()), a_flags.bits.PAUSE); // Does nothing outside dwarf mode - if (a_flags.bits.DO_MEGA && (*gamemode != game_mode::ADVENTURE || world->units.active.empty() || world->units.active[0]->counters.unconscious <= 0)) + bool adv_unconscious = (*gamemode == game_mode::ADVENTURE && !world->units.active.empty() && world->units.active[0]->counters.unconscious > 0); + + if (a_flags.bits.DO_MEGA && !adv_unconscious) showPopupAnnouncement(message, r.color, r.bright); vector results; @@ -1776,8 +1770,10 @@ bool Gui::autoDFAnnouncement(df::report_init r, string message) return true; } - bool success = false; // only print to gamelog if report was used size_t new_report_index = world->status.reports.size(); // we need this for addCombatReport + bool success = false; // only print to gamelog if report was used + bool display = ((*gamemode == game_mode::ADVENTURE && a_flags.bits.A_DISPLAY) || (*gamemode == game_mode::DWARF && a_flags.bits.D_DISPLAY)); + for (size_t i = 0; i < results.size(); i++) { // Generate report entries for each line auto new_report = new df::report(); @@ -1801,10 +1797,10 @@ bool Gui::autoDFAnnouncement(df::report_init r, string message) if (i > 0) new_report->flags.bits.continuation = true; - if (*gamemode == game_mode::ADVENTURE && !world->units.active.empty() && world->units.active[0]->counters.unconscious > 0) + if (adv_unconscious) new_report->flags.bits.unconscious = true; - if ((*gamemode == game_mode::ADVENTURE && a_flags.bits.A_DISPLAY) || (*gamemode == game_mode::DWARF && a_flags.bits.D_DISPLAY)) + if (display) { insert_into_vector(world->status.announcements, &df::report::id, new_report); new_report->flags.bits.announcement = true; @@ -1940,7 +1936,7 @@ Gui::DwarfmodeDims getDwarfmodeViewDims_default() int menu_pos = (ui_menu_width ? (*ui_menu_width)[0] : 2); int area_pos = (ui_menu_width ? (*ui_menu_width)[1] : 3); - if (ui && ui->main.mode && menu_pos >= area_pos) + if (ui && ui->main.mode != ui_sidebar_mode::Default && ui->main.mode != ui_sidebar_mode::ArenaWeather && menu_pos >= area_pos) { dims.menu_forced = true; menu_pos = area_pos-1; From ae4446610b5a00f0c91f18286519f362f5fefc79 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sat, 4 Jun 2022 15:36:50 -0700 Subject: [PATCH 26/32] Update Gui.cpp --- library/modules/Gui.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 1959c54a1..30577c5f8 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -1472,6 +1472,9 @@ static bool add_proper_report(df::unit *unit, bool is_sparring, int report_index DFHACK_EXPORT int Gui::makeAnnouncement(df::announcement_type type, df::announcement_flags flags, df::coord pos, std::string message, int color, bool bright) { + using df::global::cur_year; + using df::global::cur_year_tick; + if (gamemode == NULL || cur_year == NULL || cur_year_tick == NULL) { return -1; @@ -1523,8 +1526,8 @@ DFHACK_EXPORT int Gui::makeAnnouncement(df::announcement_type type, df::announce new_rep->color = color; new_rep->bright = bright; - new_rep->year = *df::global::cur_year; - new_rep->time = *df::global::cur_year_tick; + new_rep->year = *cur_year; + new_rep->time = *cur_year_tick; new_rep->flags.bits.continuation = continued; continued = true; From ce36abce47d5285f9e03faf159b223e9863cd716 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Sat, 4 Jun 2022 15:46:02 -0700 Subject: [PATCH 27/32] Fixes --- library/modules/Gui.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 30577c5f8..84049045e 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -1481,7 +1481,7 @@ DFHACK_EXPORT int Gui::makeAnnouncement(df::announcement_type type, df::announce } else if (message.empty()) { - Core::printerr("Empty announcement %u\n", r.type); // DF would print this to errorlog.txt + Core::printerr("Empty announcement %u\n", type); // DF would print this to errorlog.txt return -1; } @@ -1516,7 +1516,7 @@ DFHACK_EXPORT int Gui::makeAnnouncement(df::announcement_type type, df::announce // Generate the report objects int report_idx = world->status.reports.size(); bool continued = false; - bool display = ((*gamemode == game_mode::ADVENTURE && a_flags.bits.A_DISPLAY) || (*gamemode == game_mode::DWARF && a_flags.bits.D_DISPLAY)); + bool display = ((*gamemode == game_mode::ADVENTURE && flags.bits.A_DISPLAY) || (*gamemode == game_mode::DWARF && flags.bits.D_DISPLAY)); for (size_t i = 0; i < results.size(); i++) { From ad1a3408e69e01125a6d80160e9c7a179ef56f4a Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Mon, 6 Jun 2022 01:41:17 -0700 Subject: [PATCH 28/32] Hopefully fix submodules --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 1dfe6c5ab..a24581cc5 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 1dfe6c5ab9887507cdcdebdd9390352fe0bba2dd +Subproject commit a24581cc5318bdbc6227f368f67bc03a9082c19c diff --git a/scripts b/scripts index 741c84ada..52e21c5e0 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 741c84ada2ec7fdd0083744afab294d9a1b6e370 +Subproject commit 52e21c5e0c78e17acdad23f94efffb043290d5b5 From 5d08e5ae676f4a9273447b3a61d5a614bfddf848 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Mon, 6 Jun 2022 01:56:11 -0700 Subject: [PATCH 29/32] More constants; remove extra "using" statements --- library/modules/Gui.cpp | 51 +++++++++++++++-------------------------- 1 file changed, 19 insertions(+), 32 deletions(-) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 84049045e..8bc5a7473 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -113,8 +113,10 @@ using namespace DFHack; #include "df/viewscreen_workshop_profilest.h" #include "df/world.h" -const size_t MAX_REPORTS_SIZE = 3000; -const int32_t RECENT_REPORT_TICKS = 500; +const size_t MAX_REPORTS_SIZE = 3000; // DF clears old reports to maintain this vector size +const int32_t RECENT_REPORT_TICKS = 500; // used by UNIT_COMBAT_REPORT_ALL_ACTIVE +const int32_t ANNOUNCE_LINE_DURATION = 100; // time to display each line in announcement bar; 3.3 sec at 30 GFPS +const int16_t ANNOUNCE_DISPLAY_TIME = 2000; // DF uses this value for most announcements; 66.6 sec at 30 GFPS namespace DFHack { @@ -679,8 +681,6 @@ bool Gui::cursor_hotkey(df::viewscreen *top) bool Gui::workshop_job_hotkey(df::viewscreen *top) { using namespace ui_sidebar_mode; - using df::global::ui; - using df::global::world; using df::global::ui_workshop_in_add; using df::global::ui_workshop_job_cursor; @@ -717,7 +717,6 @@ bool Gui::workshop_job_hotkey(df::viewscreen *top) bool Gui::build_selector_hotkey(df::viewscreen *top) { using namespace ui_sidebar_mode; - using df::global::ui; using df::global::ui_build_selector; if (!dwarfmode_hotkey(top)) @@ -744,8 +743,6 @@ bool Gui::build_selector_hotkey(df::viewscreen *top) bool Gui::view_unit_hotkey(df::viewscreen *top) { - using df::global::ui; - using df::global::world; using df::global::ui_selected_unit; if (!dwarfmode_hotkey(top)) @@ -772,7 +769,6 @@ bool Gui::unit_inventory_hotkey(df::viewscreen *top) df::job *Gui::getSelectedWorkshopJob(color_ostream &out, bool quiet) { - using df::global::world; using df::global::ui_workshop_job_cursor; if (!workshop_job_hotkey(Core::getTopViewscreen())) { @@ -840,8 +836,6 @@ df::job *Gui::getSelectedJob(color_ostream &out, bool quiet) df::unit *Gui::getAnyUnit(df::viewscreen *top) { using namespace ui_sidebar_mode; - using df::global::ui; - using df::global::world; using df::global::ui_look_cursor; using df::global::ui_look_list; using df::global::ui_selected_unit; @@ -1122,13 +1116,10 @@ df::unit *Gui::getSelectedUnit(color_ostream &out, bool quiet) df::item *Gui::getAnyItem(df::viewscreen *top) { using namespace ui_sidebar_mode; - using df::global::ui; - using df::global::world; using df::global::ui_look_cursor; using df::global::ui_look_list; using df::global::ui_unit_view_mode; using df::global::ui_building_item_cursor; - using df::global::ui_sidebar_menus; if (VIRTUAL_CAST_VAR(screen, df::viewscreen_textviewerst, top)) { @@ -1256,11 +1247,8 @@ df::item *Gui::getSelectedItem(color_ostream &out, bool quiet) df::building *Gui::getAnyBuilding(df::viewscreen *top) { using namespace ui_sidebar_mode; - using df::global::ui; using df::global::ui_look_list; using df::global::ui_look_cursor; - using df::global::world; - using df::global::ui_sidebar_menus; if (VIRTUAL_CAST_VAR(screen, df::viewscreen_buildinglistst, top)) return vector_get(screen->buildings, screen->cursor); @@ -1323,8 +1311,6 @@ df::building *Gui::getSelectedBuilding(color_ostream &out, bool quiet) df::plant *Gui::getAnyPlant(df::viewscreen *top) { using df::global::cursor; - using df::global::ui; - using df::global::world; if (auto dfscreen = dfhack_viewscreen::try_cast(top)) return dfscreen->getSelectedPlant(); @@ -1452,7 +1438,7 @@ static int32_t check_repeat_report(vector &results) if (offset == results.size()) // all lines matched { - reports[base]->duration = 100; + reports[base]->duration = ANNOUNCE_LINE_DURATION; // display the last line again return ++(reports[base]->repeat_count); } } @@ -1504,7 +1490,7 @@ DFHACK_EXPORT int Gui::makeAnnouncement(df::announcement_type type, df::announce { if (flags.bits.D_DISPLAY) { - world->status.display_timer = 2000; + world->status.display_timer = ANNOUNCE_DISPLAY_TIME; Gui::writeToGamelog("x" + to_string(repeat_count + 1)); } return -1; @@ -1543,7 +1529,7 @@ DFHACK_EXPORT int Gui::makeAnnouncement(df::announcement_type type, df::announce { insert_into_vector(world->status.announcements, &df::report::id, new_rep); new_rep->flags.bits.announcement = true; - world->status.display_timer = 2000; + world->status.display_timer = ANNOUNCE_DISPLAY_TIME; } } @@ -1761,7 +1747,7 @@ bool Gui::autoDFAnnouncement(df::report_init r, string message) } // Check for repeat report - int32_t repeat_count = check_repeat_report(results); // Does nothing outside dwarf mode + int32_t repeat_count = check_repeat_report(results); // always returns 0 outside dwarf mode if (repeat_count > 0) { if (a_flags.bits.D_DISPLAY) @@ -1863,6 +1849,7 @@ bool Gui::autoDFAnnouncement(df::announcement_type type, df::coord pos, std::str r.color = color; r.bright = bright; r.pos = pos; + r.display_timer = ANNOUNCE_DISPLAY_TIME; r.unit1 = unit1; r.unit2 = unit2; r.flags.bits.hostile_combat = !is_sparring; @@ -2135,17 +2122,17 @@ bool Gui::setCursorCoords (const int32_t x, const int32_t y, const int32_t z) bool Gui::getDesignationCoords (int32_t &x, int32_t &y, int32_t &z) { - x = df::global::selection_rect->start_x; - y = df::global::selection_rect->start_y; - z = df::global::selection_rect->start_z; + x = selection_rect->start_x; + y = selection_rect->start_y; + z = selection_rect->start_z; return (x == -30000) ? false : true; } bool Gui::setDesignationCoords (const int32_t x, const int32_t y, const int32_t z) { - df::global::selection_rect->start_x = x; - df::global::selection_rect->start_y = y; - df::global::selection_rect->start_z = z; + selection_rect->start_x = x; + selection_rect->start_y = y; + selection_rect->start_z = z; return true; } @@ -2189,14 +2176,14 @@ bool Gui::getWindowSize (int32_t &width, int32_t &height) bool Gui::getMenuWidth(uint8_t &menu_width, uint8_t &area_map_width) { - menu_width = (*df::global::ui_menu_width)[0]; - area_map_width = (*df::global::ui_menu_width)[1]; + menu_width = (*ui_menu_width)[0]; + area_map_width = (*ui_menu_width)[1]; return true; } bool Gui::setMenuWidth(const uint8_t menu_width, const uint8_t area_map_width) { - (*df::global::ui_menu_width)[0] = menu_width; - (*df::global::ui_menu_width)[1] = area_map_width; + (*ui_menu_width)[0] = menu_width; + (*ui_menu_width)[1] = area_map_width; return true; } From a7267e3c4ecfddae81447e5748d3ba4ed88b4b70 Mon Sep 17 00:00:00 2001 From: Ryan Williams Date: Tue, 7 Jun 2022 04:21:48 -0700 Subject: [PATCH 30/32] Optimize report deletion --- library/modules/Gui.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 8bc5a7473..82da223ea 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -1415,15 +1415,19 @@ static bool recent_report_any(df::unit *unit) static void delete_old_reports() { auto &reports = world->status.reports; - while (reports.size() > MAX_REPORTS_SIZE) + if (reports.size() > MAX_REPORTS_SIZE) { - if (reports[0] != NULL) + size_t excess = reports.size() - MAX_REPORTS_SIZE; + for (size_t i = 0; i < excess; i++) { - if (reports[0]->flags.bits.announcement) - erase_from_vector(world->status.announcements, &df::report::id, reports[0]->id); - delete reports[0]; + if (reports[i] != NULL) + { // report destructor + if (reports[i]->flags.bits.announcement) + erase_from_vector(world->status.announcements, &df::report::id, reports[i]->id); + delete reports[i]; + } } - reports.erase(reports.begin()); + reports.erase(reports.begin(), reports.begin() + excess); } } From 24b237ae25dd10cbefd31fc27743a5ef8766e03f Mon Sep 17 00:00:00 2001 From: Myk Date: Fri, 18 Nov 2022 17:35:50 -0800 Subject: [PATCH 31/32] Update changelog.txt --- docs/changelog.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 0003c875e..d0bfa789f 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -80,6 +80,8 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## API - ``Gui::anywhere_hotkey``: for plugin commands bound to keybindings that can be invoked on any screen +- add functions reverse-engineered from announcement code: ``Gui::autoDFAnnouncement``, ``Gui::pauseRecenter`` +- ``Gui::revealInDwarfmodeMap``: Now enforce valid view bounds when pos invalid, add variant accepting x, y, z - ``Lua::PushInterfaceKeys()``: transforms viewscreen ``feed()`` keys into something that can be interpreted by lua-based widgets - ``Lua::Push()``: now handles maps with otherwise supported keys and values - Units module: added new checks @@ -203,8 +205,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 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. ## API -- add functions reverse-engineered from announcement code: ``Gui::autoDFAnnouncement``, ``Gui::pauseRecenter`` -- ``Gui::revealInDwarfmodeMap``: Now enforce valid view bounds when pos invalid, add variant accepting x, y, z - ``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``. ## Lua From ac5a1d35ae19d4e7c3b4003842d17d1151e44110 Mon Sep 17 00:00:00 2001 From: Myk Date: Fri, 18 Nov 2022 17:39:49 -0800 Subject: [PATCH 32/32] remove duplicate function definition --- library/LuaApi.cpp | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 51a58d948..36d132124 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1554,26 +1554,6 @@ static int gui_autoDFAnnouncement(lua_State *state) return 1; } -static int gui_getDwarfmodeViewDims(lua_State *state) -{ - auto dims = Gui::getDwarfmodeViewDims(); - lua_newtable(state); - Lua::TableInsert(state, "map_x1", dims.map_x1); - Lua::TableInsert(state, "map_x2", dims.map_x2); - Lua::TableInsert(state, "menu_x1", dims.menu_x1); - Lua::TableInsert(state, "menu_x2", dims.menu_x2); - Lua::TableInsert(state, "area_x1", dims.area_x1); - Lua::TableInsert(state, "area_x2", dims.area_x2); - Lua::TableInsert(state, "y1", dims.y1); - Lua::TableInsert(state, "y2", dims.y2); - Lua::TableInsert(state, "map_y1", dims.map_y1); - Lua::TableInsert(state, "map_y2", dims.map_y2); - Lua::TableInsert(state, "menu_on", dims.menu_on); - Lua::TableInsert(state, "area_on", dims.area_on); - Lua::TableInsert(state, "menu_forced", dims.menu_forced); - return 1; -} - static int gui_pauseRecenter(lua_State *state) { bool rv;