From 80ec5ab043828e1a237e488a0ad6b372ccf496b0 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Sun, 13 Dec 2015 14:03:25 -0600 Subject: [PATCH 01/60] vim-style +-args. for example, "./dfhack +echo foo" would be equivalent to running ./dfhack and then typing "echo foo" into the console. uses shell argument splitting, so "./dfhack +somecommand 'foo bar' baz" does the right thing. See DFHack#755. --- library/Core.cpp | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/library/Core.cpp b/library/Core.cpp index bdbb5c3b2..283fca927 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -62,6 +62,7 @@ using namespace std; using namespace DFHack; #include "df/ui.h" +#include "df/ui_sidebar_menus.h" #include "df/world.h" #include "df/world_data.h" #include "df/interfacest.h" @@ -1549,6 +1550,39 @@ bool Core::Init() if (!server->listen(RemoteClient::GetDefaultPort())) cerr << "TCP listen failed.\n"; + vector & args = df::global::ui_sidebar_menus->unk.anon_2; + for (auto it = args.begin(); it != args.end(); ) + { + const string & first = **it; + if (first.length() > 0 && first[0] == '+') + { + vector cmd; + for (it++; it != args.end(); it++) { + const string & arg = **it; + if (arg.length() > 0 && arg[0] == '+') + { + break; + } + cmd.push_back(arg); + } + + color_ostream_proxy out(getConsole()); + if (runCommand(out, first.substr(1), cmd) != CR_OK) + { + cerr << "Error running command: " << first.substr(1); + for (auto it2 = cmd.begin(); it2 != cmd.end(); it2++) + { + cerr << " " << *it2; + } + cerr << "\n"; + } + } + else + { + it++; + } + } + cerr << "DFHack is running.\n"; return true; } From 9bccf457c5d7ef8fc1628dbe78d997e4d9f43637 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Thu, 17 Dec 2015 10:54:19 -0600 Subject: [PATCH 02/60] Check for ui_sidebar_menus being null before using it. --- library/Core.cpp | 49 +++++++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index 283fca927..0da2d2b28 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1550,36 +1550,39 @@ bool Core::Init() if (!server->listen(RemoteClient::GetDefaultPort())) cerr << "TCP listen failed.\n"; - vector & args = df::global::ui_sidebar_menus->unk.anon_2; - for (auto it = args.begin(); it != args.end(); ) + if (df::global::ui_sidebar_menus) { - const string & first = **it; - if (first.length() > 0 && first[0] == '+') + vector & args = df::global::ui_sidebar_menus->unk.anon_2; + for (auto it = args.begin(); it != args.end(); ) { - vector cmd; - for (it++; it != args.end(); it++) { - const string & arg = **it; - if (arg.length() > 0 && arg[0] == '+') - { - break; + const string & first = **it; + if (first.length() > 0 && first[0] == '+') + { + vector cmd; + for (it++; it != args.end(); it++) { + const string & arg = **it; + if (arg.length() > 0 && arg[0] == '+') + { + break; + } + cmd.push_back(arg); } - cmd.push_back(arg); - } - color_ostream_proxy out(getConsole()); - if (runCommand(out, first.substr(1), cmd) != CR_OK) - { - cerr << "Error running command: " << first.substr(1); - for (auto it2 = cmd.begin(); it2 != cmd.end(); it2++) + color_ostream_proxy out(getConsole()); + if (runCommand(out, first.substr(1), cmd) != CR_OK) { - cerr << " " << *it2; + cerr << "Error running command: " << first.substr(1); + for (auto it2 = cmd.begin(); it2 != cmd.end(); it2++) + { + cerr << " " << *it2; + } + cerr << "\n"; } - cerr << "\n"; } - } - else - { - it++; + else + { + it++; + } } } From 7d9f2d0c08748428c9201e6ed4ee06bdc697b3af Mon Sep 17 00:00:00 2001 From: moversti Date: Thu, 24 Dec 2015 00:31:23 +0200 Subject: [PATCH 03/60] Updated Windows compile docs Needing cmake.portable instead of cmake --- docs/Compile.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Compile.rst b/docs/Compile.rst index f17206d8b..81719c902 100644 --- a/docs/Compile.rst +++ b/docs/Compile.rst @@ -363,7 +363,7 @@ To install Chocolatey and the required dependencies: * Close this ``cmd.exe`` window and open another Admin ``cmd.exe`` in the same way * Run the following command:: - choco install git cmake strawberryperl -y + choco install git cmake.portable strawberryperl -y * Close the Admin ``cmd.exe`` window; you're done! From de160de0e8fe721e5ec8353cef07ad01935d5a16 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 24 Dec 2015 21:34:38 -0500 Subject: [PATCH 04/60] showmood: use correct console encoding --- NEWS.rst | 5 +++++ plugins/showmood.cpp | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/NEWS.rst b/NEWS.rst index e7fbe0ef7..ec7fa5454 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -36,6 +36,11 @@ New Features ------------ - `search-plugin`: Support for the location occupation assignment menu +Fixes +----- +- `showmood`: Fixed name display on OS X/Linux + + DFHack 0.40.24-r5 ================= diff --git a/plugins/showmood.cpp b/plugins/showmood.cpp index c1e4ff019..05e9a67ad 100644 --- a/plugins/showmood.cpp +++ b/plugins/showmood.cpp @@ -69,7 +69,7 @@ command_result df_showmood (color_ostream &out, vector & parameters) out.printerr("Dwarf with strange mood does not have a mood type!\n"); continue; } - out.print("%s is currently ", Translation::TranslateName(&unit->name, false).c_str()); + out.print("%s is currently ", DF2CONSOLE(Translation::TranslateName(&unit->name, false)).c_str()); switch (unit->mood) { case mood_type::Macabre: From 7fe71c94f6ea9c2f97a2ac994d356518bf9c2ee6 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 25 Dec 2015 10:05:13 -0500 Subject: [PATCH 05/60] A few devel/unit-path fixes * Handle unrecognized unit_path_type values * Handle getSelectedUnit() * Only zoom to path.dest if it's valid --- scripts/devel/unit-path.lua | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/scripts/devel/unit-path.lua b/scripts/devel/unit-path.lua index d05203b71..2eff7ed2d 100644 --- a/scripts/devel/unit-path.lua +++ b/scripts/devel/unit-path.lua @@ -20,11 +20,16 @@ UnitPathUI.focus_path = 'unit-path' UnitPathUI.ATTRS { unit = DEFAULT_NIL, + has_path = false, + has_goal = false, } function UnitPathUI:init() self.saved_mode = df.global.ui.main.mode - + if self.unit then + self.has_path = #self.unit.path.path.x > 0 + self.has_goal = self.unit.path.dest.x >= 0 + end end function UnitPathUI:onShow() @@ -139,12 +144,11 @@ function UnitPathUI:onRenderBody(dc) end local cursor = guidm.getCursorPos() - local has_path = #self.unit.path.path.x>0 local vp = self:getViewport() local mdc = gui.Painter.new(self.df_layout.map) - if not has_path then + if not self.has_path then if gui.blink_visible(120) then paintMapTile(mdc, vp, cursor, self.unit.pos, 15, COLOR_LIGHTRED, COLOR_RED) end @@ -153,7 +157,7 @@ function UnitPathUI:onRenderBody(dc) else self:renderPath(mdc,vp,cursor) - dc:seek(1,6):pen(COLOR_GREEN):string(df.unit_path_goal[self.unit.path.goal]) + dc:seek(1,6):pen(COLOR_GREEN):string(df.unit_path_goal[self.unit.path.goal] or '?') end dc:newline():pen(COLOR_GREY) @@ -183,7 +187,7 @@ function UnitPathUI:onRenderBody(dc) dc:newline():newline(1):pen(COLOR_WHITE) dc:key('CUSTOM_Z'):string(": Zoom unit, ") - dc:key('CUSTOM_G'):string(": Zoom goal",COLOR_GREY,nil,has_path) + dc:key('CUSTOM_G'):string(": Zoom goal",COLOR_GREY,nil,self.has_goal) dc:newline(1) dc:key('CUSTOM_N'):string(": Zoom station",COLOR_GREY,nil,has_station) dc:newline():newline(1) @@ -194,7 +198,7 @@ function UnitPathUI:onInput(keys) if keys.CUSTOM_Z then self:moveCursorTo(copyall(self.unit.pos)) elseif keys.CUSTOM_G then - if #self.unit.path.path.x > 0 then + if self.has_goal then self:moveCursorTo(copyall(self.unit.path.dest)) end elseif keys.CUSTOM_N then @@ -208,6 +212,10 @@ function UnitPathUI:onInput(keys) end end +function UnitPathUI:onGetSelectedUnit() + return self.unit +end + local unit = dfhack.gui.getSelectedUnit(true) if not unit or not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/ViewUnits/Some/') then From d4b631d9fda9dd862aeba0d09efb5e9fc884a7c0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 25 Dec 2015 10:53:48 -0500 Subject: [PATCH 06/60] tweak prerelease pack warning --- scripts/gui/prerelease-warning.lua | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/scripts/gui/prerelease-warning.lua b/scripts/gui/prerelease-warning.lua index a065e5224..bb72cbafc 100644 --- a/scripts/gui/prerelease-warning.lua +++ b/scripts/gui/prerelease-warning.lua @@ -13,22 +13,25 @@ local gui = require 'gui' local dlg = require 'gui.dialogs' local utils = require 'utils' -local message = { +message = { 'This is a prerelease build of DFHack. Some structures are likely', NEWLINE, 'to be incorrect, resulting in crashes or save corruption', NEWLINE, - {pen=COLOR_LIGHTRED, text='Make backups of your saves and avoid saving if possible.'}, NEWLINE, + {pen=COLOR_LIGHTRED, text='Make backups of your saves and avoid saving if possible.'}, } +pack_message = pack_message or [[ + +This should not be enabled by default in a pack. +If you are seeing this message and did not enable/install DFHack +yourself, please report this to your pack's maintainer.]] + path = dfhack.getHackPath():lower() -if path:find('lnp') or path:find('starter') or path:find('newb') or path:find('lazy') or path:find('pack') then - local pack_msg = [[ -Under no circumstances should this be enabled by default in a pack. -If you are seeing this message and did not enable DFHack yourself, -please report this to your pack's maintainer.]] - for _, v in pairs(utils.split_string(pack_msg, '\n')) do +if #pack_message > 0 and (path:find('lnp') or path:find('starter') or path:find('newb') or path:find('lazy') or path:find('pack')) then + for _, v in pairs(utils.split_string(pack_message, '\n')) do table.insert(message, NEWLINE) table.insert(message, {text=v, pen=COLOR_LIGHTMAGENTA}) end + pack_message = '' end dfhack.print('\n') From 64d861bf6b99e8411395bc0f6b0f0ac99fc197e0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 25 Dec 2015 11:09:05 -0500 Subject: [PATCH 07/60] Add support for viewscreen_locationsst to various gui functions --- library/modules/Gui.cpp | 24 ++++++++++++++++++++++++ library/xml | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 8de672ec6..518fe9bda 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -62,6 +62,7 @@ using namespace DFHack; #include "df/viewscreen_layer_assigntradest.h" #include "df/viewscreen_layer_militaryst.h" #include "df/viewscreen_layer_stockpilest.h" +#include "df/viewscreen_locationsst.h" #include "df/viewscreen_petst.h" #include "df/viewscreen_tradegoodsst.h" #include "df/viewscreen_storesst.h" @@ -542,6 +543,11 @@ DEFINE_GET_FOCUS_STRING_HANDLER(layer_stockpile) } } +DEFINE_GET_FOCUS_STRING_HANDLER(locations) +{ + focus += "/" + enum_item_key(screen->menu); +} + std::string Gui::getFocusString(df::viewscreen *top) { if (!top) @@ -835,6 +841,24 @@ df::unit *Gui::getAnyUnit(df::viewscreen *top) } } + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_locationsst, top)) + { + switch (screen->menu) + { + case df::viewscreen_locationsst::AssignOccupation: + return vector_get(screen->units, screen->unit_idx); + case df::viewscreen_locationsst::Occupations: + { + auto occ = vector_get(screen->occupations, screen->occupation_idx); + if (occ) + return df::unit::find(occ->unit_id); + return NULL; + } + default: + return NULL; + } + } + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_petst, top)) { switch (screen->mode) diff --git a/library/xml b/library/xml index 636f77875..73a475830 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 636f7787552cc663f01561c7dc84f758cea027f1 +Subproject commit 73a4758307b01396ed28c8df011a6f95867450cf From a9ad2f40f1db79b0153083bd2beb7b3235502552 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 25 Dec 2015 11:18:55 -0500 Subject: [PATCH 08/60] Add a location retirement confirmation --- NEWS.rst | 1 + plugins/confirm.cpp | 2 ++ plugins/lua/confirm.lua | 11 +++++++++++ 3 files changed, 14 insertions(+) diff --git a/NEWS.rst b/NEWS.rst index ec7fa5454..5d4ef51e3 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -34,6 +34,7 @@ DFHack future New Features ------------ +- `confirm`: Added a confirmation for retiring locations - `search-plugin`: Support for the location occupation assignment menu Fixes diff --git a/plugins/confirm.cpp b/plugins/confirm.cpp index ec8ee2a73..3ea5d540c 100644 --- a/plugins/confirm.cpp +++ b/plugins/confirm.cpp @@ -19,6 +19,7 @@ #include "df/general_ref_contained_in_itemst.h" #include "df/viewscreen_dwarfmodest.h" #include "df/viewscreen_layer_militaryst.h" +#include "df/viewscreen_locationsst.h" #include "df/viewscreen_tradegoodsst.h" using namespace DFHack; @@ -453,6 +454,7 @@ DEFINE_CONFIRMATION(squad_disband, viewscreen_layer_militaryst, 0); DEFINE_CONFIRMATION(uniform_delete, viewscreen_layer_militaryst, 0); DEFINE_CONFIRMATION(note_delete, viewscreen_dwarfmodest, 0); DEFINE_CONFIRMATION(route_delete, viewscreen_dwarfmodest, 0); +DEFINE_CONFIRMATION(location_retire, viewscreen_locationsst, 0); DFhackCExport command_result plugin_init (color_ostream &out, vector &commands) { diff --git a/plugins/lua/confirm.lua b/plugins/lua/confirm.lua index 751b1b4da..135187358 100644 --- a/plugins/lua/confirm.lua +++ b/plugins/lua/confirm.lua @@ -192,6 +192,17 @@ end route_delete.title = "Delete route" route_delete.message = "Are you sure you want to delete this route?" +location_retire = defconf('location-retire') +function location_retire.intercept_key(key) + return key == keys.LOCATION_RETIRE and + (screen.menu == df.viewscreen_locationsst.T_menu.Locations or + screen.menu == df.viewscreen_locationsst.T_menu.Occupations) and + screen.in_edit == df.viewscreen_locationsst.T_in_edit.None and + screen.locations[screen.location_idx] +end +location_retire.title = "Retire location" +location_retire.message = "Are you sure you want to retire this location?" + -- End of confirmation definitions function check() From 05926d9734925f88aba21db6f16eed4f20851b9c Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 26 Dec 2015 11:47:55 -0500 Subject: [PATCH 09/60] New tweak: title-start-rename Adds a safe rename option to the title screen "Start Playing" menu --- NEWS.rst | 5 +- docs/Plugins.rst | 1 + library/xml | 2 +- plugins/tweak/tweak.cpp | 7 ++ plugins/tweak/tweaks/title-start-rename.h | 112 ++++++++++++++++++++++ 5 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 plugins/tweak/tweaks/title-start-rename.h diff --git a/NEWS.rst b/NEWS.rst index 5d4ef51e3..4444ded39 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -9,7 +9,7 @@ Internals Lua - New [Internal Commands | Plugins | Scripts | Tweaks] + New [Internal Commands | Plugins | Scripts | Features] Fixes Misc Improvements Removed @@ -36,6 +36,9 @@ New Features ------------ - `confirm`: Added a confirmation for retiring locations - `search-plugin`: Support for the location occupation assignment menu +- `tweak`: + + - ``tweak title-start-rename``: Adds a safe rename option to the title screen "Start Playing" menu Fixes ----- diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 677d75c82..952813180 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -315,6 +315,7 @@ Subcommands that persist until disabled or DF quits: :nestbox-color: Fixes the color of built nestboxes :shift-8-scroll: Gives Shift-8 (or :kbd:`*`) priority when scrolling menus, instead of scrolling the map :stable-cursor: Saves the exact cursor position between t/q/k/d/b/etc menus of fortress mode. +:title-start-rename: Adds a safe rename option to the title screen "Start Playing" menu :tradereq-pet-gender: Displays pet genders on the trade request screen .. _fix-armory: diff --git a/library/xml b/library/xml index 73a475830..11dfc8f83 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 73a4758307b01396ed28c8df011a6f95867450cf +Subproject commit 11dfc8f838617ee5d42524a838345491704d9dff diff --git a/plugins/tweak/tweak.cpp b/plugins/tweak/tweak.cpp index 98ec21eb3..6b7f11603 100644 --- a/plugins/tweak/tweak.cpp +++ b/plugins/tweak/tweak.cpp @@ -14,6 +14,7 @@ #include "modules/Materials.h" #include "modules/MapCache.h" #include "modules/Buildings.h" +#include "modules/Filesystem.h" #include "MiscUtils.h" @@ -96,6 +97,7 @@ #include "tweaks/nestbox-color.h" #include "tweaks/shift-8-scroll.h" #include "tweaks/stable-cursor.h" +#include "tweaks/title-start-rename.h" #include "tweaks/tradereq-pet-gender.h" using std::set; @@ -218,6 +220,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector save_dir).c_str(), full_save_dir(entry).c_str()) != 0) + return false; + save->save_dir = entry; + entry = ""; + return true; + } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + INTERPOSE_NEXT(render)(); + if (sel_subpage == T_sel_subpage::StartSelectWorld || sel_subpage == T_sel_subpage::StartSelectMode) + { + auto save = get_cur_save(); + if (save) + { + int x = 1, y = 7; + OutputHotkeyString(x, y, + in_rename ? entry.c_str() : "Rename", + df::interface_key::CUSTOM_R, + false, 0, + rename_failed ? COLOR_LIGHTRED : COLOR_WHITE, + in_rename ? COLOR_RED : COLOR_LIGHTRED); + if (in_rename) + OutputString(COLOR_LIGHTGREEN, x, y, "_"); + } + } + } + + DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set *input)) + { + using namespace df::enums::interface_key; + if (in_rename) + { + rename_failed = false; + auto string_key = get_string_key(input); + if (input->count(SELECT) && !entry.empty()) + { + if (do_rename()) + in_rename = false; + else + rename_failed = true; + } + else if (input->count(STRING_A000)) + { + if (!entry.empty()) + entry.erase(entry.size() - 1); + } + else if (string_key != NONE) + { + entry += Screen::keyToChar(string_key); + } + else if (input->count(LEAVESCREEN) || (input->count(SELECT) && entry.empty()) || + input->count(STANDARDSCROLL_UP) || input->count(STANDARDSCROLL_DOWN)) + { + entry = ""; + in_rename = false; + std::set tmp; + if (input->count(STANDARDSCROLL_UP)) + tmp.insert(STANDARDSCROLL_UP); + if (input->count(STANDARDSCROLL_DOWN)) + tmp.insert(STANDARDSCROLL_DOWN); + INTERPOSE_NEXT(feed)(&tmp); + } + } + else if (input->count(CUSTOM_R)) + { + in_rename = true; + } + else + { + INTERPOSE_NEXT(feed)(input); + } + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(title_start_rename_hook, render); +IMPLEMENT_VMETHOD_INTERPOSE(title_start_rename_hook, feed); + +df::viewscreen_titlest::T_sel_subpage title_start_rename_hook::last_subpage = + df::viewscreen_titlest::T_sel_subpage::None; +bool title_start_rename_hook::in_rename = false; +bool title_start_rename_hook::rename_failed = false; +std::string title_start_rename_hook::entry; From 106891f6e09eff657fe6f9e09edb6666fe8e51d6 Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Sun, 13 Dec 2015 14:03:25 -0600 Subject: [PATCH 10/60] vim-style +-args. for example, "./dfhack +echo foo" would be equivalent to running ./dfhack and then typing "echo foo" into the console. uses shell argument splitting, so "./dfhack +somecommand 'foo bar' baz" does the right thing. See DFHack#755. --- library/Core.cpp | 63 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/library/Core.cpp b/library/Core.cpp index e58db7755..9c2ef6403 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -62,6 +62,7 @@ using namespace std; using namespace DFHack; #include "df/ui.h" +#include "df/ui_sidebar_menus.h" #include "df/world.h" #include "df/world_data.h" #include "df/interfacest.h" @@ -1549,6 +1550,68 @@ bool Core::Init() if (!server->listen(RemoteClient::GetDefaultPort())) cerr << "TCP listen failed.\n"; + if (df::global::ui_sidebar_menus) + { + vector args; + const string & raw = df::global::ui_sidebar_menus->command_line.raw; + size_t offset = 0; + while (offset < raw.size()) + { + if (raw[offset] == '"') + { + offset++; + size_t next = raw.find("\"", offset); + args.push_back(raw.substr(offset, next - offset)); + offset = next + 2; + } + else + { + size_t next = raw.find(" ", offset); + if (next == string::npos) + { + args.push_back(raw.substr(offset)); + offset = raw.size(); + } + else + { + args.push_back(raw.substr(offset, next - offset)); + offset = next + 1; + } + } + } + for (auto it = args.begin(); it != args.end(); ) + { + const string & first = *it; + if (first.length() > 0 && first[0] == '+') + { + vector cmd; + for (it++; it != args.end(); it++) { + const string & arg = *it; + if (arg.length() > 0 && arg[0] == '+') + { + break; + } + cmd.push_back(arg); + } + + color_ostream_proxy out(getConsole()); + if (runCommand(out, first.substr(1), cmd) != CR_OK) + { + cerr << "Error running command: " << first.substr(1); + for (auto it2 = cmd.begin(); it2 != cmd.end(); it2++) + { + cerr << " \"" << *it2 << "\""; + } + cerr << "\n"; + } + } + else + { + it++; + } + } + } + cerr << "DFHack is running.\n"; return true; } From c8b937b1158295f3fcd43a6a6306c86a1c11c021 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 26 Dec 2015 14:25:25 -0500 Subject: [PATCH 11/60] Update 3rdparty/lethosor (install devel/send-key) --- scripts/3rdparty/lethosor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/3rdparty/lethosor b/scripts/3rdparty/lethosor index 01b594027..26c600132 160000 --- a/scripts/3rdparty/lethosor +++ b/scripts/3rdparty/lethosor @@ -1 +1 @@ -Subproject commit 01b594027a84c6b732f7639870538f138483a78c +Subproject commit 26c60013223e02b5558e31bed8e0625495430995 From fabff1ddfb015190942771b60c54f68705c4a7b7 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 26 Dec 2015 19:59:54 -0500 Subject: [PATCH 12/60] Fix df::occupation usage --- library/modules/Gui.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 518fe9bda..ae7c97236 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -90,6 +90,7 @@ using namespace DFHack; #include "df/route_stockpile_link.h" #include "df/game_mode.h" #include "df/unit.h" +#include "df/occupation.h" using namespace df::enums; using df::global::gview; From a977f274b16b37f8531a54402dabcf0cfa9ca074 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 26 Dec 2015 20:00:09 -0500 Subject: [PATCH 13/60] Update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 11dfc8f83..cc74796a9 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 11dfc8f838617ee5d42524a838345491704d9dff +Subproject commit cc74796a90b139e6f7ca7291d5161f84d7beb985 From d087f4f741c74b4bc8dd915690ff89ddc188b954 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 27 Dec 2015 14:59:18 -0500 Subject: [PATCH 14/60] Expose process MD5/PE to Lua (useful in export-dt-ini) --- docs/Lua API.rst | 8 ++++++++ library/LuaApi.cpp | 20 ++++++++++++++++++++ library/Process-darwin.cpp | 7 ++++--- library/Process-linux.cpp | 7 ++++--- library/Process-windows.cpp | 6 +++--- library/include/MemAccess.h | 5 +++++ scripts/devel/export-dt-ini.lua | 9 +++++++-- 7 files changed, 51 insertions(+), 11 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 2a3db9b16..c090b8a9d 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -1908,6 +1908,14 @@ and are only documented here for completeness: The table used by ``dfhack.run_script()`` to give every script its own global environment, persistent between calls to the script. +* ``dfhack.internal.getPE()`` + + Returns the PE timestamp of the DF executable (only on Windows) + +* ``dfhack.internal.getMD5()`` + + Returns the MD5 of the DF executable (only on OS X and Linux) + * ``dfhack.internal.getAddress(name)`` Returns the global address ``name``, or *nil*. diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index a3a9eb070..ba9591719 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -2304,6 +2304,24 @@ static const LuaWrapper::FunctionReg dfhack_internal_module[] = { { NULL, NULL } }; +static int internal_getmd5(lua_State *L) +{ + auto p = Core::getInstance().p; + if (p->getDescriptor()->getOS() == OS_WINDOWS) + luaL_error(L, "process MD5 not available on Windows"); + lua_pushstring(L, p->getMD5().c_str()); + return 1; +} + +static int internal_getPE(lua_State *L) +{ + auto p = Core::getInstance().p; + if (p->getDescriptor()->getOS() != OS_WINDOWS) + luaL_error(L, "process PE timestamp not available on non-Windows"); + lua_pushinteger(L, p->getPE()); + return 1; +} + static int internal_getAddress(lua_State *L) { const char *name = luaL_checkstring(L, 1); @@ -2683,6 +2701,8 @@ static int internal_findScript(lua_State *L) } static const luaL_Reg dfhack_internal_funcs[] = { + { "getPE", internal_getPE }, + { "getMD5", internal_getmd5 }, { "getAddress", internal_getAddress }, { "setAddress", internal_setAddress }, { "getVTable", internal_getVTable }, diff --git a/library/Process-darwin.cpp b/library/Process-darwin.cpp index a485e196d..6f159e5fe 100644 --- a/library/Process-darwin.cpp +++ b/library/Process-darwin.cpp @@ -60,15 +60,16 @@ Process::Process(VersionInfoFactory * known_versions) identified = false; my_descriptor = 0; + my_pe = 0; md5wrapper md5; uint32_t length; uint8_t first_kb [1024]; memset(first_kb, 0, sizeof(first_kb)); // get hash of the running DF process - string hash = md5.getHashFromFile(real_path, length, (char *) first_kb); + my_md5 = md5.getHashFromFile(real_path, length, (char *) first_kb); // create linux process, add it to the vector - VersionInfo * vinfo = known_versions->getVersionInfoByMD5(hash); + VersionInfo * vinfo = known_versions->getVersionInfoByMD5(my_md5); if(vinfo) { my_descriptor = new VersionInfo(*vinfo); @@ -79,7 +80,7 @@ Process::Process(VersionInfoFactory * known_versions) char * wd = getcwd(NULL, 0); cerr << "Unable to retrieve version information.\n"; cerr << "File: " << real_path << endl; - cerr << "MD5: " << hash << endl; + cerr << "MD5: " << my_md5 << endl; cerr << "working dir: " << wd << endl; cerr << "length:" << length << endl; cerr << "1KB hexdump follows:" << endl; diff --git a/library/Process-linux.cpp b/library/Process-linux.cpp index 1b430e5a8..e4c647f7f 100644 --- a/library/Process-linux.cpp +++ b/library/Process-linux.cpp @@ -55,15 +55,16 @@ Process::Process(VersionInfoFactory * known_versions) identified = false; my_descriptor = 0; + my_pe = 0; md5wrapper md5; uint32_t length; uint8_t first_kb [1024]; memset(first_kb, 0, sizeof(first_kb)); // get hash of the running DF process - string hash = md5.getHashFromFile(exe_link_name, length, (char *) first_kb); + my_md5 = md5.getHashFromFile(exe_link_name, length, (char *) first_kb); // create linux process, add it to the vector - VersionInfo * vinfo = known_versions->getVersionInfoByMD5(hash); + VersionInfo * vinfo = known_versions->getVersionInfoByMD5(my_md5); if(vinfo) { my_descriptor = new VersionInfo(*vinfo); @@ -74,7 +75,7 @@ Process::Process(VersionInfoFactory * known_versions) char * wd = getcwd(NULL, 0); cerr << "Unable to retrieve version information.\n"; cerr << "File: " << exe_link_name << endl; - cerr << "MD5: " << hash << endl; + cerr << "MD5: " << my_md5 << endl; cerr << "working dir: " << wd << endl; cerr << "length:" << length << endl; cerr << "1KB hexdump follows:" << endl; diff --git a/library/Process-windows.cpp b/library/Process-windows.cpp index c923441e3..5474d7cfb 100644 --- a/library/Process-windows.cpp +++ b/library/Process-windows.cpp @@ -95,7 +95,8 @@ Process::Process(VersionInfoFactory * factory) { return; } - VersionInfo* vinfo = factory->getVersionInfoByPETimestamp(d->pe_header.FileHeader.TimeDateStamp); + my_pe = d->pe_header.FileHeader.TimeDateStamp; + VersionInfo* vinfo = factory->getVersionInfoByPETimestamp(my_pe); if(vinfo) { identified = true; @@ -105,8 +106,7 @@ Process::Process(VersionInfoFactory * factory) } else { - fprintf(stderr, "Unable to retrieve version information.\nPE timestamp: 0x%x\n", - d->pe_header.FileHeader.TimeDateStamp); + fprintf(stderr, "Unable to retrieve version information.\nPE timestamp: 0x%x\n", my_pe); fflush(stderr); } } diff --git a/library/include/MemAccess.h b/library/include/MemAccess.h index 80844ae5f..36100b400 100644 --- a/library/include/MemAccess.h +++ b/library/include/MemAccess.h @@ -287,6 +287,9 @@ namespace DFHack EXEC = 4 }; + uint32_t getPE() { return my_pe; } + std::string getMD5() { return my_md5; } + private: VersionInfo * my_descriptor; PlatformSpecific *d; @@ -294,6 +297,8 @@ namespace DFHack uint32_t my_pid; uint32_t base; std::map classNameCache; + uint32_t my_pe; + std::string my_md5; }; class DFHACK_EXPORT ClassNameCheck diff --git a/scripts/devel/export-dt-ini.lua b/scripts/devel/export-dt-ini.lua index 1d99b2d55..c75acd1dd 100644 --- a/scripts/devel/export-dt-ini.lua +++ b/scripts/devel/export-dt-ini.lua @@ -421,8 +421,13 @@ address('uniform_indiv_choice',df.squad_uniform_spec,'indiv_choice') local out = io.open('therapist.ini', 'w') out:write('[info]\n') --- TODO: add an api function to retrieve the checksum -out:write('checksum=<>\n') +if dfhack.getOSType() == 'windows' and dfhack.internal.getPE then + out:write(('checksum=0x%x\n'):format(dfhack.internal.getPE())) +elseif dfhack.getOSType() ~= 'windows' and dfhack.internal.getMD5 then + out:write(('checksum=0x%s\n'):format(dfhack.internal.getMD5():sub(1, 8))) +else + out:write('checksum=<>\n') +end out:write('version_name='..dfhack.getDFVersion()..'\n') out:write('complete='..(complete and 'true' or 'false')..'\n') From e075a064861fb3ea0cfee507105e8898c9130265 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 27 Dec 2015 15:49:10 -0500 Subject: [PATCH 15/60] Add search controls to all viewscreen_petst subpages --- NEWS.rst | 7 ++- library/xml | 2 +- plugins/search.cpp | 141 ++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 140 insertions(+), 10 deletions(-) diff --git a/NEWS.rst b/NEWS.rst index 04e99c056..cab6c0c69 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -44,7 +44,12 @@ Internals New Features ------------ - `confirm`: Added a confirmation for retiring locations -- `search-plugin`: Support for the location occupation assignment menu +- `search-plugin`: Support for new screens: + + - location occupation assignment + - civilization animal training knowledge + - animal trainer assignment + - `tweak`: - ``tweak title-start-rename``: Adds a safe rename option to the title screen "Start Playing" menu diff --git a/library/xml b/library/xml index cc74796a9..c8f486bf7 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit cc74796a90b139e6f7ca7291d5161f84d7beb985 +Subproject commit c8f486bf79ee456ef7a9ed5ed938263fd3703466 diff --git a/plugins/search.cpp b/plugins/search.cpp index cd94b3320..83d498367 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -7,6 +7,7 @@ #include "uicommon.h" +#include "df/creature_raw.h" #include "df/ui_look_list.h" #include "df/viewscreen_announcelistst.h" #include "df/viewscreen_petst.h" @@ -621,7 +622,7 @@ protected: }; // This basic match function is separated out from the generic multi column class, because the -// pets screen, which uses a union in its primary list, will cause a compile failure is this +// pets screen, which uses a union in its primary list, will cause a compile failure if this // match function exists in the generic class template < class S, class T, class PARENT = search_generic > class search_multicolumn_modifiable : public search_multicolumn_modifiable_generic @@ -751,7 +752,7 @@ template V generic_search_hook ::module; typedef generic_search_hook module##_hook; \ template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(module##_hook, feed, prio); \ template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(module##_hook, render, prio); \ - template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(module##_hook, key_conflict, prio) + template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(module##_hook, key_conflict, prio); // // END: Generic Search functionality @@ -761,19 +762,24 @@ template V generic_search_hook ::module; // // START: Animal screen search // -typedef df::viewscreen_petst::T_animal T_animal; -typedef df::viewscreen_petst::T_mode T_mode; -class pets_search : public search_multicolumn_modifiable_generic +typedef search_multicolumn_modifiable_generic pets_search_base; +class pets_search : public pets_search_base { + typedef df::viewscreen_petst::T_animal T_animal; + typedef df::viewscreen_petst::T_mode T_mode; public: void render() const { - if (viewscreen->mode == T_mode::List) - print_search_option(25, 4); + print_search_option(25, 4); } private: + bool can_init(df::viewscreen_petst *screen) + { + return pets_search_base::can_init(screen) && screen->mode == T_mode::List; + } + int32_t *get_viewscreen_cursor() { return &viewscreen->cursor; @@ -876,13 +882,130 @@ private: std::vector *is_adopting, is_adopting_s; }; -IMPLEMENT_HOOKS(df::viewscreen_petst, pets_search); +IMPLEMENT_HOOKS_WITH_ID(df::viewscreen_petst, pets_search, 1, 0); // // END: Animal screen search // +// +// START: Animal knowledge screen search +// + +typedef search_generic animal_knowledge_search_base; +class animal_knowledge_search : public animal_knowledge_search_base +{ + typedef df::viewscreen_petst::T_mode T_mode; + bool can_init(df::viewscreen_petst *screen) + { + return animal_knowledge_search_base::can_init(screen) && screen->mode == T_mode::TrainingKnowledge; + } + +public: + void render() const + { + print_search_option(2, 4); + } + +private: + int32_t *get_viewscreen_cursor() + { + return NULL; + } + + vector *get_primary_list() + { + return &viewscreen->known; + } + + string get_element_description(int32_t id) const + { + auto craw = df::creature_raw::find(id); + string out; + if (craw) + { + for (size_t i = 0; i < 3; ++i) + out += craw->name[i] + " "; + } + return out; + } +}; + +IMPLEMENT_HOOKS_WITH_ID(df::viewscreen_petst, animal_knowledge_search, 2, 0); + +// +// END: Animal knowledge screen search +// + + +// +// START: Animal trainer search +// + +typedef search_twocolumn_modifiable animal_trainer_search_base; +class animal_trainer_search : public animal_trainer_search_base +{ + typedef df::viewscreen_petst::T_mode T_mode; + typedef df::viewscreen_petst::T_trainer_mode T_trainer_mode; + + bool can_init(df::viewscreen_petst *screen) + { + return animal_trainer_search_base::can_init(screen) && screen->mode == T_mode::SelectTrainer; + } + +public: + void render() const + { + Screen::paintTile(Screen::Pen(186, 8, 0), 14, 2); + Screen::paintTile(Screen::Pen(186, 8, 0), gps->dimx - 14, 2); + Screen::paintTile(Screen::Pen(201, 8, 0), 14, 1); + Screen::paintTile(Screen::Pen(187, 8, 0), gps->dimx - 14, 1); + for (int x = 15; x <= gps->dimx - 15; ++x) + { + Screen::paintTile(Screen::Pen(205, 8, 0), x, 1); + Screen::paintTile(Screen::Pen(0, 0, 0), x, 2); + } + print_search_option(16, 2); + } + +private: + int32_t *get_viewscreen_cursor() + { + return &viewscreen->trainer_cursor; + } + + vector *get_primary_list() + { + return &viewscreen->trainer_unit; + } + + string get_element_description(df::unit *u) const + { + return get_unit_description(u); + } + + std::vector *get_secondary_list() + { + return &viewscreen->trainer_mode; + } + +public: + bool process_input(set *input) + { + if (input->count(interface_key::SELECT) && viewscreen->trainer_unit.empty() && !in_entry_mode()) + return true; + return animal_trainer_search_base::process_input(input); + } + +}; + +IMPLEMENT_HOOKS_WITH_ID(df::viewscreen_petst, animal_trainer_search, 3, 0); + +// +// END: Animal trainer search +// + // // START: Stocks screen search @@ -1997,6 +2120,8 @@ IMPLEMENT_HOOKS(df::viewscreen_locationsst, location_assign_occupation_search); HOOK_ACTION(trade_search_fort_hook) \ HOOK_ACTION(stocks_search_hook) \ HOOK_ACTION(pets_search_hook) \ + HOOK_ACTION(animal_knowledge_search_hook) \ + HOOK_ACTION(animal_trainer_search_hook) \ HOOK_ACTION(military_search_hook) \ HOOK_ACTION(nobles_search_hook) \ HOOK_ACTION(profiles_search_hook) \ From c23ac50250e9c4137c8c5fc38c44e41ed2940c71 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 27 Dec 2015 16:39:10 -0500 Subject: [PATCH 16/60] Always run dfhack.init-example if dfhack.init doesn't exist This previously only happened if no files matching *dfhack*.init existed --- library/Core.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Core.cpp b/library/Core.cpp index 61a1cc2b8..00447fe72 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1256,7 +1256,7 @@ static void run_dfhack_init(color_ostream &out, Core *core) std::vector prefixes(1, "dfhack"); size_t count = loadScriptFiles(core, out, prefixes, "."); - if (!count) + if (!count || !Filesystem::isfile("dfhack.init")) { core->runCommand(out, "gui/no-dfhack-init"); core->loadScriptFile(out, "dfhack.init-example", true); From 2ef24a3daf58d186cbeb00c7d60e7150d70f6202 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 27 Dec 2015 22:35:14 -0500 Subject: [PATCH 17/60] Prevent nested command invocations in command-prompt For example, running "devel/send-key SELECT" in command-prompt would blow up the stack by recursively calling submit() --- plugins/command-prompt.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/command-prompt.cpp b/plugins/command-prompt.cpp index 2ac4d3af9..dd3b59398 100644 --- a/plugins/command-prompt.cpp +++ b/plugins/command-prompt.cpp @@ -56,7 +56,7 @@ public: df::building* getSelectedBuilding() { return Gui::getAnyBuilding(parent); } std::string getFocusString() { return "commandprompt"; } - viewscreen_commandpromptst(std::string entry):is_response(false) + viewscreen_commandpromptst(std::string entry):is_response(false), submitted(false) { show_fps=gps->display_frames; gps->display_frames=0; @@ -127,6 +127,7 @@ protected: std::list > responses; int cursor_pos; int history_idx; + bool submitted; bool is_response; bool show_fps; int frame; @@ -194,6 +195,9 @@ void viewscreen_commandpromptst::submit() Screen::dismiss(this); return; } + if(submitted) + return; + submitted = true; prompt_ostream out(this); Core::getInstance().runCommand(out, get_entry()); if(out.empty() && responses.empty()) From 39086acf64b246a34a663e4c7293dc4e2b2aec01 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 28 Dec 2015 15:46:42 -0500 Subject: [PATCH 18/60] Merge export-dt-ini changes from splintermind/Dwarf-Therapist --- library/xml | 2 +- scripts/devel/export-dt-ini.lua | 94 ++++++++++++++++++++++----------- 2 files changed, 63 insertions(+), 33 deletions(-) diff --git a/library/xml b/library/xml index c8f486bf7..1017578b0 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit c8f486bf79ee456ef7a9ed5ed938263fd3703466 +Subproject commit 1017578b0e32257aefa39bcc2444b8830100ff74 diff --git a/scripts/devel/export-dt-ini.lua b/scripts/devel/export-dt-ini.lua index c75acd1dd..0bb344d5c 100644 --- a/scripts/devel/export-dt-ini.lua +++ b/scripts/devel/export-dt-ini.lua @@ -1,10 +1,8 @@ -- Exports an ini file for Dwarf Therapist. --[[=begin - devel/export-dt-ini =================== Exports an ini file containing memory addresses for Dwarf Therapist. - =end]] local utils = require 'utils' @@ -66,22 +64,39 @@ end -- List of actual values header('addresses') -address('translation_vector',globals,'world','raws','language','translations') -address('language_vector',globals,'world','raws','language','words') -address('creature_vector',globals,'world','units','all') -address('active_creature_vector',globals,'world','units','active') -address('dwarf_race_index',globals,'ui','race_id') -address('squad_vector',globals,'world','squads','all') -address('current_year',globals,'cur_year') address('cur_year_tick',globals,'cur_year_tick') +address('current_year',globals,'cur_year') address('dwarf_civ_index',globals,'ui','civ_id') -address('races_vector',globals,'world','raws','creatures','all') -address('reactions_vector',globals,'world','raws','reactions') -address('events_vector',globals,'world','history','events') -address('historical_figures_vector',globals,'world','history','figures') -address('fake_identities_vector',globals,'world','identities','all') +address('dwarf_race_index',globals,'ui','race_id') address('fortress_entity',globals,'ui','main','fortress_entity') address('historical_entities_vector',globals,'world','entities','all') +address('creature_vector',globals,'world','units','all') +address('active_creature_vector',globals,'world','units','active') +address('weapons_vector',globals,'world','items','other','WEAPON') +address('shields_vector',globals,'world','items','other', 'SHIELD') +address('quivers_vector',globals,'world','items','other', 'QUIVER') +address('crutches_vector',globals,'world','items','other', 'CRUTCH') +address('backpacks_vector',globals,'world','items','other', 'BACKPACK') +address('ammo_vector',globals,'world','items','other', 'AMMO') +address('flasks_vector',globals,'world','items','other', 'FLASK') +address('pants_vector',globals,'world','items','other', 'PANTS') +address('armor_vector',globals,'world','items','other', 'ARMOR') +address('shoes_vector',globals,'world','items','other', 'SHOES') +address('helms_vector',globals,'world','items','other', 'HELM') +address('gloves_vector',globals,'world','items','other', 'GLOVES') +address('artifacts_vector',globals,'world','artifacts','all') +address('squad_vector',globals,'world','squads','all') +address('activities_vector',globals,'world','activities','all') +address('fake_identities_vector',globals,'world','identities','all') +address('poetic_forms_vector',globals,'world','poetic_forms','all') +address('musical_forms_vector',globals,'world','musical_forms','all') +address('dance_forms_vector',globals,'world','dance_forms','all') +address('occupations_vector',globals,'world','occupations','all') +address('world_data',globals,'world','world_data') +address('material_templates_vector',globals,'world','raws','material_templates') +address('inorganics_vector',globals,'world','raws','inorganics') +address('plants_vector',globals,'world','raws','plants','all') +address('races_vector',globals,'world','raws','creatures','all') address('itemdef_weapons_vector',globals,'world','raws','itemdefs','weapons') address('itemdef_trap_vector',globals,'world','raws','itemdefs','trapcomps') address('itemdef_toy_vector',globals,'world','raws','itemdefs','toys') @@ -96,29 +111,17 @@ address('itemdef_shield_vector',globals,'world','raws','itemdefs','shields') address('itemdef_helm_vector',globals,'world','raws','itemdefs','helms') address('itemdef_pant_vector',globals,'world','raws','itemdefs','pants') address('itemdef_food_vector',globals,'world','raws','itemdefs','food') +address('language_vector',globals,'world','raws','language','words') +address('translation_vector',globals,'world','raws','language','translations') address('colors_vector',globals,'world','raws','language','colors') address('shapes_vector',globals,'world','raws','language','shapes') +address('reactions_vector',globals,'world','raws','reactions') address('base_materials',globals,'world','raws','mat_table','builtin') -address('inorganics_vector',globals,'world','raws','inorganics') -address('plants_vector',globals,'world','raws','plants','all') -address('material_templates_vector',globals,'world','raws','material_templates') address('all_syndromes_vector',globals,'world','raws','syndromes','all') -address('world_data',globals,'world','world_data') -address('active_sites_vector',df.world_data,'active_site') +address('events_vector',globals,'world','history','events') +address('historical_figures_vector',globals,'world','history','figures') address('world_site_type',df.world_site,'type') -address('weapons_vector',globals,'world','items','other','WEAPON') -address('shields_vector',globals,'world','items','other', 'SHIELD') -address('quivers_vector',globals,'world','items','other', 'QUIVER') -address('crutches_vector',globals,'world','items','other', 'CRUTCH') -address('backpacks_vector',globals,'world','items','other', 'BACKPACK') -address('ammo_vector',globals,'world','items','other', 'AMMO') -address('flasks_vector',globals,'world','items','other', 'FLASK') -address('pants_vector',globals,'world','items','other', 'PANTS') -address('armor_vector',globals,'world','items','other', 'ARMOR') -address('shoes_vector',globals,'world','items','other', 'SHOES') -address('helms_vector',globals,'world','items','other', 'HELM') -address('gloves_vector',globals,'world','items','other', 'GLOVES') -address('artifacts_vector',globals,'world','artifacts','all') +address('active_sites_vector',df.world_data,'active_site') header('offsets') address('word_table',df.language_translation,'words') @@ -220,6 +223,7 @@ address('stack_size',df.item_actual,'stack_size') address('wear',df.item_actual,'wear') address('mat_type',df.item_crafted,'mat_type') address('mat_index',df.item_crafted,'mat_index') +address('maker_race',df.item_crafted,'maker_race') address('quality',df.item_crafted,'quality') header('item_subtype_offsets') @@ -400,9 +404,20 @@ address('id',df.squad,'id') address('name',df.squad,'name') address('alias',df.squad,'alias') address('members',df.squad,'positions') +address('orders',df.squad,'orders') +address('schedules',df.squad,'schedule') +if os_type ~= 'windows' then --squad_schedule_entry size + value('sched_size',0x20) +else + value('sched_size',0x40) +end +address('sched_orders',df.squad_schedule_entry,'orders') +address('sched_assign',df.squad_schedule_entry,'order_assignments') +address('alert',df.squad,'cur_alert_idx') address('carry_food',df.squad,'carry_food') address('carry_water',df.squad,'carry_water') address('ammunition',df.squad,'ammunition') +address('ammunition_qty',df.squad_ammo_spec,'amount') address('quiver',df.squad_position,'quiver') address('backpack',df.squad_position,'backpack') address('flask',df.squad_position,'flask') @@ -416,6 +431,21 @@ address('weapon_vector',df.squad_position,'uniform','weapon') address('uniform_item_filter',df.squad_uniform_spec,'item_filter') address('uniform_indiv_choice',df.squad_uniform_spec,'indiv_choice') +header('activity_offsets') +address('activity_type',df.activity_entry,'id') +address('events',df.activity_entry,'events') +address('participants',df.activity_event_combat_trainingst,'participants') +address('sq_lead',df.activity_event_skill_demonstrationst,'hist_figure_id') +address('sq_skill',df.activity_event_skill_demonstrationst,'skill') +address('sq_train_rounds',df.activity_event_skill_demonstrationst,'train_rounds') +address('pray_deity',df.activity_event_prayerst,'histfig_id') +address('pray_sphere',df.activity_event_prayerst,'topic') +address('knowledge_category',df.activity_event_ponder_topicst,'knowledge_category') +address('knowledge_flag',df.activity_event_ponder_topicst,'knowledge_flag') +address('perf_type',df.activity_event_performancest,'type') +address('perf_participants',df.activity_event_performancest,'activity_event_performancest::anon2') +address('perf_histfig',df.activity_event_performancest::anon2,'histfig_id') + -- Final creation of the file local out = io.open('therapist.ini', 'w') From 9dbb5b92f9f234198c5644929df946c70ef84b64 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 28 Dec 2015 15:55:09 -0500 Subject: [PATCH 19/60] Fix some issues in devel/export-dt-ini.lua --- scripts/devel/export-dt-ini.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/devel/export-dt-ini.lua b/scripts/devel/export-dt-ini.lua index 0bb344d5c..28a583a2d 100644 --- a/scripts/devel/export-dt-ini.lua +++ b/scripts/devel/export-dt-ini.lua @@ -443,8 +443,8 @@ address('pray_sphere',df.activity_event_prayerst,'topic') address('knowledge_category',df.activity_event_ponder_topicst,'knowledge_category') address('knowledge_flag',df.activity_event_ponder_topicst,'knowledge_flag') address('perf_type',df.activity_event_performancest,'type') -address('perf_participants',df.activity_event_performancest,'activity_event_performancest::anon2') -address('perf_histfig',df.activity_event_performancest::anon2,'histfig_id') +address('perf_participants',df.activity_event_performancest,'participants') +address('perf_histfig',df.activity_event_performancest,'anon_2') -- Final creation of the file From 6637a8516405419f8fd97106ceadf2e0b5a95a45 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 28 Dec 2015 16:31:36 -0500 Subject: [PATCH 20/60] Don't hardcode line numbers in script documentation check --- travis/script-in-readme.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/travis/script-in-readme.py b/travis/script-in-readme.py index a6804fb00..b8ad67b65 100644 --- a/travis/script-in-readme.py +++ b/travis/script-in-readme.py @@ -1,3 +1,4 @@ +from __future__ import print_function from io import open import os from os.path import basename, dirname, join, splitext @@ -16,6 +17,8 @@ def check_file(fname): errors, doclines = 0, [] with open(fname, errors='ignore') as f: for l in f.readlines(): + if not l.strip(): + continue if doclines or l.strip().endswith('=begin'): doclines.append(l.rstrip()) if l.startswith('=end'): @@ -26,7 +29,7 @@ def check_file(fname): else: print('Error: no documentation in: ' + fname) return 1 - title, underline = doclines[2], doclines[3] + title, underline = doclines[1], doclines[2] if underline != '=' * len(title): print('Error: title/underline mismatch:', fname, title, underline) errors += 1 From 7431329e2df547fb0cec39a5ff209e1696a369f3 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 28 Dec 2015 17:01:47 -0500 Subject: [PATCH 21/60] New script: devel/save-version.lua --- NEWS.rst | 4 + scripts/devel/save-version.lua | 146 +++++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 scripts/devel/save-version.lua diff --git a/NEWS.rst b/NEWS.rst index cab6c0c69..c213b165f 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -41,6 +41,10 @@ Internals ./dfhack +devel/print-args example "Dwarf Fortress.exe" +devel/print-args example +New Scripts +----------- +- `devel/save-version`: Displays DF version information about the current save + New Features ------------ - `confirm`: Added a confirmation for retiring locations diff --git a/scripts/devel/save-version.lua b/scripts/devel/save-version.lua new file mode 100644 index 000000000..5b346c1d5 --- /dev/null +++ b/scripts/devel/save-version.lua @@ -0,0 +1,146 @@ +-- Display DF version information about the current save +--@module = true +--[[=begin + +devel/save-version +================== +Display DF version information about the current save + +=end]] + +local function dummy() return nil end + +function has_field(tbl, field) + return (pcall(function() assert(tbl[field] ~= nil) end)) +end + +function class_has_field(cls, field) + local obj = cls:new() + local ret = has_field(obj, field) + obj:delete() + return ret +end + +versions = { +-- skipped v0.21-v0.28 + [1287] = "0.31.01", + [1288] = "0.31.02", + [1289] = "0.31.03", + [1292] = "0.31.04", + [1295] = "0.31.05", + [1297] = "0.31.06", + [1300] = "0.31.08", + [1304] = "0.31.09", + [1305] = "0.31.10", + [1310] = "0.31.11", + [1311] = "0.31.12", + [1323] = "0.31.13", + [1325] = "0.31.14", + [1326] = "0.31.15", + [1327] = "0.31.16", + [1340] = "0.31.17", + [1341] = "0.31.18", + [1351] = "0.31.19", + [1353] = "0.31.20", + [1354] = "0.31.21", + [1359] = "0.31.22", + [1360] = "0.31.23", + [1361] = "0.31.24", + [1362] = "0.31.25", + [1372] = "0.34.01", + [1374] = "0.34.02", + [1376] = "0.34.03", + [1377] = "0.34.04", + [1378] = "0.34.05", + [1382] = "0.34.06", + [1383] = "0.34.07", + [1400] = "0.34.08", + [1402] = "0.34.09", + [1403] = "0.34.10", + [1404] = "0.34.11", + [1441] = "0.40.01", + [1442] = "0.40.02", + [1443] = "0.40.03", + [1444] = "0.40.04", + [1445] = "0.40.05", + [1446] = "0.40.06", + [1448] = "0.40.07", + [1449] = "0.40.08", + [1451] = "0.40.09", + [1452] = "0.40.10", + [1456] = "0.40.11", + [1459] = "0.40.12", + [1462] = "0.40.13", + [1469] = "0.40.14", + [1470] = "0.40.15", + [1471] = "0.40.16", + [1472] = "0.40.17", + [1473] = "0.40.18", + [1474] = "0.40.19", + [1477] = "0.40.20", + [1478] = "0.40.21", + [1479] = "0.40.22", + [1480] = "0.40.23", + [1481] = "0.40.24", + [1531] = "0.42.01", + [1532] = "0.42.02", + [1533] = "0.42.03", + [1534] = "0.42.04", +} + +min_version = math.huge +max_version = -math.huge + +for k in pairs(versions) do + min_version = math.min(min_version, k) + max_version = math.max(max_version, k) +end + +if class_has_field(df.world.T_cur_savegame, 'save_version') then + function get_save_version() + return df.global.world.cur_savegame.save_version + end +elseif class_has_field(df.world.T_pathfinder, 'anon_2') then + function get_save_version() + return df.global.world.pathfinder.anon_2 + end +else + get_save_version = dummy +end + +if class_has_field(df.world, 'original_save_version') then + function get_original_save_version() + return df.global.world.original_save_version + end +else + get_original_save_version = dummy +end + +function describe(version) + if version == 0 then + return 'no world loaded' + elseif versions[version] then + return versions[version] + elseif version < min_version then + return 'unknown old version before ' .. describe(min_version) .. ': ' .. tostring(version) + elseif version > max_version then + return 'unknown new version after ' .. describe(max_version) .. ': ' .. tostring(version) + else + return 'unknown version: ' .. tostring(version) + end +end + +function dump(desc, func) + local ret = tonumber(func()) + if ret then + print(desc .. ': ' .. describe(ret)) + else + dfhack.printerr('could not find ' .. desc .. ' (DFHack version too old)') + end +end + +if not moduleMode then + if not dfhack.isWorldLoaded() then qerror('no world loaded') end + dump('original DF version', get_original_save_version) + dump('most recent DF version', get_save_version) +end From 0a4287afba895cae9f8b50c8a30453eed2afdd66 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 28 Dec 2015 17:28:50 -0500 Subject: [PATCH 22/60] gps scan: fall back to data section --- scripts/devel/find-offsets.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/devel/find-offsets.lua b/scripts/devel/find-offsets.lua index a105c1350..8718dea6e 100644 --- a/scripts/devel/find-offsets.lua +++ b/scripts/devel/find-offsets.lua @@ -530,6 +530,9 @@ local function find_gps() local w,h = ms.get_screen_size() local idx, addr = zone.area.int32_t:find_one{w, h, -1, -1} + if not idx then + idx, addr = data.int32_t.find_one{w, h, -1, -1} + end if idx then validate_offset('gps', is_valid_gps, addr, df.graphic, 'dimx') return From 22283066caf48854f40e005f20894e662faf9203 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 28 Dec 2015 17:30:05 -0500 Subject: [PATCH 23/60] Update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 1017578b0..15775d808 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 1017578b0e32257aefa39bcc2444b8830100ff74 +Subproject commit 15775d808cded53ddcbe2675bdbe03b0f123c707 From 51164d554773c016bd4576052902b1f2681afbcb Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 28 Dec 2015 21:35:47 -0500 Subject: [PATCH 24/60] Add standing orders scans to find-offsets --- scripts/devel/find-offsets.lua | 119 +++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/scripts/devel/find-offsets.lua b/scripts/devel/find-offsets.lua index 8718dea6e..368e8ac98 100644 --- a/scripts/devel/find-offsets.lua +++ b/scripts/devel/find-offsets.lua @@ -1584,6 +1584,50 @@ Searching for pause_state. Please do as instructed below:]], ms.found_offset('pause_state', addr) end +-- +-- standing orders +-- + +local function find_standing_orders(gname, seq, depends) + if type(seq) ~= 'table' then seq = {seq} end + for k, v in pairs(depends) do + if not dfhack.internal.getAddress(k) then + qerror(('Cannot locate %s: %s not found'):format(gname, k)) + end + df.global[k] = v + end + local addr + if dwarfmode_to_top() then + addr = searcher:find_interactive( + 'Auto-searching for ' .. gname, + 'uint8_t', + function(idx) + df.global.ui.main.mode = df.ui_sidebar_mode.Orders + dwarfmode_feed_input(table.unpack(seq)) + return true + end + ) + else + dfhack.printerr("Won't scan for standing orders global manually: " .. gname) + return + end + + ms.found_offset(gname, addr) +end + +local function exec_finder_so(gname, seq, _depends) + local depends = {} + for k, v in pairs(_depends or {}) do + if k:find('standing_orders_') ~= 1 then + k = 'standing_orders_' .. k + end + depends[k] = v + end + exec_finder(function() + return find_standing_orders(gname, seq, depends) + end, gname) +end + -- -- MAIN FLOW -- @@ -1639,6 +1683,81 @@ exec_finder(find_process_jobs, 'process_jobs') exec_finder(find_process_dig, 'process_dig') exec_finder(find_pause_state, 'pause_state') +print('\nStanding orders:\n') + +exec_finder_so('standing_orders_gather_animals', 'ORDERS_GATHER_ANIMALS') +exec_finder_so('standing_orders_gather_bodies', 'ORDERS_GATHER_BODIES') +exec_finder_so('standing_orders_gather_food', 'ORDERS_GATHER_FOOD') +exec_finder_so('standing_orders_gather_furniture', 'ORDERS_GATHER_FURNITURE') +exec_finder_so('standing_orders_gather_minerals', 'ORDERS_GATHER_STONE') +exec_finder_so('standing_orders_gather_wood', 'ORDERS_GATHER_WOOD') + +exec_finder_so('standing_orders_gather_refuse', + {'ORDERS_REFUSE', 'ORDERS_REFUSE_GATHER'}) +exec_finder_so('standing_orders_gather_refuse_outside', + {'ORDERS_REFUSE', 'ORDERS_REFUSE_OUTSIDE'}, {gather_refuse=1}) +exec_finder_so('standing_orders_gather_vermin_remains', + {'ORDERS_REFUSE', 'ORDERS_REFUSE_OUTSIDE_VERMIN'}, {gather_refuse=1, gather_refuse_outside=1}) +exec_finder_so('standing_orders_dump_bones', + {'ORDERS_REFUSE', 'ORDERS_REFUSE_DUMP_BONE'}, {gather_refuse=1}) +exec_finder_so('standing_orders_dump_corpses', + {'ORDERS_REFUSE', 'ORDERS_REFUSE_DUMP_CORPSE'}, {gather_refuse=1}) +exec_finder_so('standing_orders_dump_hair', + {'ORDERS_REFUSE', 'ORDERS_REFUSE_DUMP_STRAND_TISSUE'}, {gather_refuse=1}) +exec_finder_so('standing_orders_dump_other', + {'ORDERS_REFUSE', 'ORDERS_REFUSE_DUMP_OTHER'}, {gather_refuse=1}) +exec_finder_so('standing_orders_dump_shells', + {'ORDERS_REFUSE', 'ORDERS_REFUSE_DUMP_SHELL'}, {gather_refuse=1}) +exec_finder_so('standing_orders_dump_skins', + {'ORDERS_REFUSE', 'ORDERS_REFUSE_DUMP_SKIN'}, {gather_refuse=1}) +exec_finder_so('standing_orders_dump_skulls', + {'ORDERS_REFUSE', 'ORDERS_REFUSE_DUMP_SKULL'}, {gather_refuse=1}) + + +exec_finder_so('standing_orders_auto_butcher', + {'ORDERS_WORKSHOP', 'ORDERS_BUTCHER'}) +exec_finder_so('standing_orders_auto_collect_webs', + {'ORDERS_WORKSHOP', 'ORDERS_COLLECT_WEB'}) +exec_finder_so('standing_orders_auto_fishery', + {'ORDERS_WORKSHOP', 'ORDERS_AUTO_FISHERY'}) +exec_finder_so('standing_orders_auto_kiln', + {'ORDERS_WORKSHOP', 'ORDERS_AUTO_KILN'}) +exec_finder_so('standing_orders_auto_kitchen', + {'ORDERS_WORKSHOP', 'ORDERS_AUTO_KITCHEN'}) +exec_finder_so('standing_orders_auto_loom', + {'ORDERS_WORKSHOP', 'ORDERS_LOOM'}) +exec_finder_so('standing_orders_auto_other', + {'ORDERS_WORKSHOP', 'ORDERS_AUTO_OTHER'}) +exec_finder_so('standing_orders_auto_slaughter', + {'ORDERS_WORKSHOP', 'ORDERS_SLAUGHTER'}) +exec_finder_so('standing_orders_auto_smelter', + {'ORDERS_WORKSHOP', 'ORDERS_AUTO_SMELTER'}) +exec_finder_so('standing_orders_auto_tan', + {'ORDERS_WORKSHOP', 'ORDERS_TAN'}) +exec_finder_so('standing_orders_use_dyed_cloth', + {'ORDERS_WORKSHOP', 'ORDERS_DYED_CLOTH'}) + +exec_finder_so('standing_orders_forbid_other_dead_items', + {'ORDERS_AUTOFORBID', 'ORDERS_FORBID_OTHER_ITEMS'}) +exec_finder_so('standing_orders_forbid_other_nohunt', + {'ORDERS_AUTOFORBID', 'ORDERS_FORBID_OTHER_CORPSE'}) +exec_finder_so('standing_orders_forbid_own_dead', + {'ORDERS_AUTOFORBID', 'ORDERS_FORBID_YOUR_CORPSE'}) +exec_finder_so('standing_orders_forbid_own_dead_items', + {'ORDERS_AUTOFORBID', 'ORDERS_FORBID_YOUR_ITEMS'}) +exec_finder_so('standing_orders_forbid_used_ammo', + {'ORDERS_AUTOFORBID', 'ORDERS_FORBID_PROJECTILE'}) + +exec_finder_so('standing_orders_farmer_harvest', 'ORDERS_ALL_HARVEST') +exec_finder_so('standing_orders_job_cancel_announce', 'ORDERS_EXCEPTIONS') +exec_finder_so('standing_orders_mix_food', 'ORDERS_MIXFOODS') + +exec_finder_so('standing_orders_zoneonly_drink', + {'ORDERS_ZONE', 'ORDERS_ZONE_DRINKING'}) +exec_finder_so('standing_orders_zoneonly_fish', + {'ORDERS_ZONE', 'ORDERS_ZONE_FISHING'}) + +dwarfmode_to_top() print('\nDone. Now exit the game with the die command and add\n'.. 'the newly-found globals to symbols.xml. You can find them\n'.. 'in stdout.log or here:\n') From 2079093d5a20ef5ffd129aedb59b40ecf68d5dbf Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 28 Dec 2015 23:19:02 -0500 Subject: [PATCH 25/60] Fix new addresses in export-dt-ini --- library/xml | 2 +- scripts/devel/export-dt-ini.lua | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/library/xml b/library/xml index 15775d808..357197817 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 15775d808cded53ddcbe2675bdbe03b0f123c707 +Subproject commit 357197817bf05677ff51287b3015e1621f4c0888 diff --git a/scripts/devel/export-dt-ini.lua b/scripts/devel/export-dt-ini.lua index 28a583a2d..180a3e011 100644 --- a/scripts/devel/export-dt-ini.lua +++ b/scripts/devel/export-dt-ini.lua @@ -443,8 +443,8 @@ address('pray_sphere',df.activity_event_prayerst,'topic') address('knowledge_category',df.activity_event_ponder_topicst,'knowledge_category') address('knowledge_flag',df.activity_event_ponder_topicst,'knowledge_flag') address('perf_type',df.activity_event_performancest,'type') -address('perf_participants',df.activity_event_performancest,'participants') -address('perf_histfig',df.activity_event_performancest,'anon_2') +address('perf_participants',df.activity_event_performancest,'participant_actions') +address('perf_histfig',df.activity_event_performancest.T_participant_actions,'histfig_id') -- Final creation of the file From bd7039f4e0540fab544bbd8a859931aa0e3a19be Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 30 Dec 2015 10:11:27 -0500 Subject: [PATCH 26/60] Don't show prerelease message during worldgen --- scripts/gui/prerelease-warning.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/gui/prerelease-warning.lua b/scripts/gui/prerelease-warning.lua index bb72cbafc..8f99e450d 100644 --- a/scripts/gui/prerelease-warning.lua +++ b/scripts/gui/prerelease-warning.lua @@ -8,6 +8,10 @@ Shows a warning on world load for pre-release builds. =end]] if not dfhack.isPrerelease() then qerror('not a prerelease build') end +-- Don't fire during worldgen +if dfhack.internal.getAddress('gametype') and df.global.gametype == df.game_type.NONE then + return +end local gui = require 'gui' local dlg = require 'gui.dialogs' From 26f66ce8e263a6da48e3933f0e784d4eef44cff9 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 30 Dec 2015 11:27:19 -0500 Subject: [PATCH 27/60] Bump version to 0.42.04 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c027374f..2a20b8dba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -97,7 +97,7 @@ if (NOT EXISTS ${dfhack_SOURCE_DIR}/library/xml/codegen.pl OR NOT EXISTS ${dfhac endif() # set up versioning. -set(DF_VERSION "0.42.03") +set(DF_VERSION "0.42.04") SET(DFHACK_RELEASE "alpha1") SET(DFHACK_PRERELEASE TRUE) From aaae6d54cddb4303f48fa468af245ad96e28d0e4 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 30 Dec 2015 12:15:01 -0500 Subject: [PATCH 28/60] exportlegends: Fix issues with entities with no race Some, such as specific site governments, have races of -1 Fixes #780 --- NEWS.rst | 1 + scripts/exportlegends.lua | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/NEWS.rst b/NEWS.rst index c213b165f..d43229487 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -60,6 +60,7 @@ New Features Fixes ----- +- `exportlegends`: Handles entities without specific races - `showmood`: Fixed name display on OS X/Linux diff --git a/scripts/exportlegends.lua b/scripts/exportlegends.lua index 3c2999412..2ff14860d 100644 --- a/scripts/exportlegends.lua +++ b/scripts/exportlegends.lua @@ -205,7 +205,9 @@ function export_more_legends_xml() for entityK, entityV in ipairs(df.global.world.entities.all) do io.write ("\t".."".."\n") io.write ("\t\t"..""..entityV.id.."".."\n") - io.write ("\t\t"..""..(df.global.world.raws.creatures.all[entityV.race].creature_id):lower().."".."\n") + if entityV.race >= 0 then + io.write ("\t\t"..""..(df.global.world.raws.creatures.all[entityV.race].creature_id):lower().."".."\n") + end io.write ("\t\t"..""..(df.historical_entity_type[entityV.type]):lower().."".."\n") if (df.historical_entity_type[entityV.type]):lower() == "religion" then -- Get worshipped figure if (entityV.unknown1b ~= nil and entityV.unknown1b.worship ~= nill and From d284d9e83ad168115d05a9ae4708a1ea58e60d24 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 30 Dec 2015 14:45:15 -0500 Subject: [PATCH 29/60] exportlegends cleanup * Remove unnecessary string concatenations (e.g. `write("".."\n")`) * Open a separate file instead of rerouting stdout - this ensures that stdout doesn't point to the xml file if the export fails for some reason * Encode artifact descriptions properly --- scripts/exportlegends.lua | 294 +++++++++++++++++++------------------- 1 file changed, 148 insertions(+), 146 deletions(-) diff --git a/scripts/exportlegends.lua b/scripts/exportlegends.lua index 2ff14860d..39b21e417 100644 --- a/scripts/exportlegends.lua +++ b/scripts/exportlegends.lua @@ -91,151 +91,153 @@ function export_more_legends_xml() local year_str = string.format('%0'..math.max(5, string.len(''..df.global.cur_year))..'d', df.global.cur_year) local date_str = year_str..string.format('-%02d-%02d', month, day) - io.output(tostring(df.global.world.cur_savegame.save_dir).."-"..date_str.."-legends_plus.xml") + local filename = df.global.world.cur_savegame.save_dir.."-"..date_str.."-legends_plus.xml" + local file = io.open(filename, 'w') + if not file then qerror("could not open file: " .. filename) end - io.write ("".."\n") - io.write ("".."\n") - io.write (""..dfhack.df2utf(dfhack.TranslateName(df.global.world.world_data.name)).."".."\n") - io.write (""..dfhack.df2utf(dfhack.TranslateName(df.global.world.world_data.name,1)).."".."\n") + file:write("\n") + file:write("\n") + file:write(""..dfhack.df2utf(dfhack.TranslateName(df.global.world.world_data.name)).."\n") + file:write(""..dfhack.df2utf(dfhack.TranslateName(df.global.world.world_data.name,1)).."\n") - io.write ("".."\n") + file:write("\n") for regionK, regionV in ipairs(df.global.world.world_data.regions) do - io.write ("\t".."".."\n") - io.write ("\t\t"..""..regionV.index.."".."\n") - io.write ("\t\t".."") + file:write("\t\n") + file:write("\t\t"..regionV.index.."\n") + file:write("\t\t") for xK, xVal in ipairs(regionV.region_coords.x) do - io.write (xVal..","..regionV.region_coords.y[xK].."|") + file:write(xVal..","..regionV.region_coords.y[xK].."|") end - io.write ("\n") - io.write ("\t".."".."\n") + file:write("\n") + file:write("\t\n") end - io.write ("".."\n") + file:write("\n") - io.write ("".."\n") + file:write("\n") for regionK, regionV in ipairs(df.global.world.world_data.underground_regions) do - io.write ("\t".."".."\n") - io.write ("\t\t"..""..regionV.index.."".."\n") - io.write ("\t\t".."") + file:write("\t\n") + file:write("\t\t"..regionV.index.."\n") + file:write("\t\t") for xK, xVal in ipairs(regionV.region_coords.x) do - io.write (xVal..","..regionV.region_coords.y[xK].."|") + file:write(xVal..","..regionV.region_coords.y[xK].."|") end - io.write ("\n") - io.write ("\t".."".."\n") + file:write("\n") + file:write("\t\n") end - io.write ("".."\n") + file:write("\n") - io.write ("".."\n") + file:write("\n") for siteK, siteV in ipairs(df.global.world.world_data.sites) do if (#siteV.buildings > 0) then - io.write ("\t".."".."\n") + file:write("\t\n") for k,v in pairs(siteV) do if (k == "id") then - io.write ("\t\t".."<"..k..">"..tostring(v).."".."\n") + file:write("\t\t<"..k..">"..tostring(v).."\n") elseif (k == "buildings") then - io.write ("\t\t".."".."\n") + file:write("\t\t\n") for buildingK, buildingV in ipairs(siteV.buildings) do - io.write ("\t\t\t".."".."\n") - io.write ("\t\t\t\t"..""..buildingV.id.."".."\n") - io.write ("\t\t\t\t"..""..df.abstract_building_type[buildingV:getType()]:lower().."".."\n") + file:write("\t\t\t\n") + file:write("\t\t\t\t"..buildingV.id.."\n") + file:write("\t\t\t\t"..df.abstract_building_type[buildingV:getType()]:lower().."\n") if (df.abstract_building_type[buildingV:getType()]:lower() ~= "underworld_spire") then - io.write ("\t\t\t\t"..""..dfhack.df2utf(dfhack.TranslateName(buildingV.name, 1)).."".."\n") - io.write ("\t\t\t\t"..""..dfhack.df2utf(dfhack.TranslateName(buildingV.name)).."".."\n") + file:write("\t\t\t\t"..dfhack.df2utf(dfhack.TranslateName(buildingV.name, 1)).."\n") + file:write("\t\t\t\t"..dfhack.df2utf(dfhack.TranslateName(buildingV.name)).."\n") end - io.write ("\t\t\t".."".."\n") + file:write("\t\t\t\n") end - io.write ("\t\t".."".."\n") + file:write("\t\t\n") end end - io.write ("\t".."".."\n") + file:write("\t\n") end end - io.write ("".."\n") + file:write("\n") - io.write ("".."\n") + file:write("\n") for wcK, wcV in ipairs(df.global.world.world_data.constructions.list) do - io.write ("\t".."".."\n") - io.write ("\t\t"..""..wcV.id.."".."\n") - io.write ("\t\t"..""..dfhack.df2utf(dfhack.TranslateName(wcV.name,1)).."".."\n") - io.write ("\t\t"..""..(df.world_construction_type[wcV:getType()]):lower().."".."\n") - io.write ("\t\t".."") + file:write("\t\n") + file:write("\t\t"..wcV.id.."\n") + file:write("\t\t"..dfhack.df2utf(dfhack.TranslateName(wcV.name,1)).."\n") + file:write("\t\t"..(df.world_construction_type[wcV:getType()]):lower().."\n") + file:write("\t\t") for xK, xVal in ipairs(wcV.square_pos.x) do - io.write (xVal..","..wcV.square_pos.y[xK].."|") + file:write(xVal..","..wcV.square_pos.y[xK].."|") end - io.write ("\n") - io.write ("\t".."".."\n") + file:write("\n") + file:write("\t\n") end - io.write ("".."\n") + file:write("\n") - io.write ("".."\n") + file:write("\n") for artifactK, artifactV in ipairs(df.global.world.artifacts.all) do - io.write ("\t".."".."\n") - io.write ("\t\t"..""..artifactV.id.."".."\n") + file:write("\t\n") + file:write("\t\t"..artifactV.id.."\n") if (artifactV.item:getType() ~= -1) then - io.write ("\t\t"..""..tostring(df.item_type[artifactV.item:getType()]):lower().."".."\n") + file:write("\t\t"..tostring(df.item_type[artifactV.item:getType()]):lower().."\n") if (artifactV.item:getSubtype() ~= -1) then - io.write ("\t\t"..""..artifactV.item.subtype.name.."".."\n") + file:write("\t\t"..artifactV.item.subtype.name.."\n") end end if (table.containskey(artifactV.item,"description")) then - io.write ("\t\t"..""..artifactV.item.description:lower().."".."\n") + file:write("\t\t"..dfhack.df2utf(artifactV.item.description:lower()).."\n") end if (artifactV.item:getMaterial() ~= -1 and artifactV.item:getMaterialIndex() ~= -1) then - io.write ("\t\t"..""..dfhack.matinfo.toString(dfhack.matinfo.decode(artifactV.item:getMaterial(), artifactV.item:getMaterialIndex())).."".."\n") + file:write("\t\t"..dfhack.matinfo.toString(dfhack.matinfo.decode(artifactV.item:getMaterial(), artifactV.item:getMaterialIndex())).."\n") end - io.write ("\t".."".."\n") + file:write("\t\n") end - io.write ("".."\n") + file:write("\n") - io.write ("".."\n".."".."\n") + file:write("\n\n") - io.write ("".."\n") + file:write("\n") for entityPopK, entityPopV in ipairs(df.global.world.entity_populations) do - io.write ("\t".."".."\n") - io.write ("\t\t"..""..entityPopV.id.."".."\n") + file:write("\t\n") + file:write("\t\t"..entityPopV.id.."\n") for raceK, raceV in ipairs(entityPopV.races) do local raceName = (df.global.world.raws.creatures.all[raceV].creature_id):lower() - io.write ("\t\t"..""..raceName..":"..entityPopV.counts[raceK].."".."\n") + file:write("\t\t"..raceName..":"..entityPopV.counts[raceK].."\n") end - io.write ("\t\t"..""..entityPopV.civ_id.."".."\n") - io.write ("\t".."".."\n") + file:write("\t\t"..entityPopV.civ_id.."\n") + file:write("\t\n") end - io.write ("".."\n") + file:write("\n") - io.write ("".."\n") + file:write("\n") for entityK, entityV in ipairs(df.global.world.entities.all) do - io.write ("\t".."".."\n") - io.write ("\t\t"..""..entityV.id.."".."\n") + file:write("\t\n") + file:write("\t\t"..entityV.id.."\n") if entityV.race >= 0 then - io.write ("\t\t"..""..(df.global.world.raws.creatures.all[entityV.race].creature_id):lower().."".."\n") + file:write("\t\t"..(df.global.world.raws.creatures.all[entityV.race].creature_id):lower().."\n") end - io.write ("\t\t"..""..(df.historical_entity_type[entityV.type]):lower().."".."\n") + file:write("\t\t"..(df.historical_entity_type[entityV.type]):lower().."\n") if (df.historical_entity_type[entityV.type]):lower() == "religion" then -- Get worshipped figure if (entityV.unknown1b ~= nil and entityV.unknown1b.worship ~= nill and #entityV.unknown1b.worship == 1) then - io.write ("\t\t"..""..entityV.unknown1b.worship[0].."".."\n") + file:write("\t\t"..entityV.unknown1b.worship[0].."\n") else print(entityV.unknown1b, entityV.unknown1b.worship, #entityV.unknown1b.worship) end end for id, link in pairs(entityV.entity_links) do - io.write ("\t\t".."".."\n") + file:write("\t\t\n") for k, v in pairs(link) do if (k == "type") then - io.write ("\t\t\t".."<"..k..">"..tostring(df.entity_entity_link_type[v]).."".."\n") + file:write("\t\t\t<"..k..">"..tostring(df.entity_entity_link_type[v]).."\n") else - io.write ("\t\t\t".."<"..k..">"..v.."".."\n") + file:write("\t\t\t<"..k..">"..v.."\n") end end - io.write ("\t\t".."".."\n") + file:write("\t\t\n") end for id, link in ipairs(entityV.children) do - io.write ("\t\t"..""..link.."".."\n") + file:write("\t\t"..link.."\n") end - io.write ("\t".."".."\n") + file:write("\t\n") end - io.write ("".."\n") + file:write("\n") - io.write ("".."\n") + file:write("\n") for ID, event in ipairs(df.global.world.history.events) do if event:getType() == df.history_event_type.ADD_HF_ENTITY_LINK or event:getType() == df.history_event_type.ADD_HF_SITE_LINK @@ -276,9 +278,9 @@ function export_more_legends_xml() or event:getType() == df.history_event_type.HIST_FIGURE_WOUNDED or event:getType() == df.history_event_type.HIST_FIGURE_DIED then - io.write ("\t".."".."\n") - io.write ("\t\t"..""..event.id.."".."\n") - io.write ("\t\t"..""..tostring(df.history_event_type[event:getType()]):lower().."".."\n") + file:write("\t\n") + file:write("\t\t"..event.id.."\n") + file:write("\t\t"..tostring(df.history_event_type[event:getType()]):lower().."\n") for k,v in pairs(event) do if k == "year" or k == "seconds" or k == "flags" or k == "id" or (k == "region" and event:getType() ~= df.history_event_type.HF_DOES_INTERACTION) @@ -286,70 +288,70 @@ function export_more_legends_xml() or k == "anon_1" or k == "anon_2" or k == "flags2" or k == "unk1" then elseif event:getType() == df.history_event_type.ADD_HF_ENTITY_LINK and k == "link_type" then - io.write ("\t\t".."<"..k..">"..df.histfig_entity_link_type[v]:lower().."".."\n") + file:write("\t\t<"..k..">"..df.histfig_entity_link_type[v]:lower().."\n") elseif event:getType() == df.history_event_type.ADD_HF_ENTITY_LINK and k == "position_id" then local entity = findEntity(event.civ) if (entity ~= nil and event.civ > -1 and v > -1) then for entitypositionsK, entityPositionsV in ipairs(entity.positions.own) do if entityPositionsV.id == v then - io.write ("\t\t"..""..tostring(entityPositionsV.name[0]):lower().."".."\n") + file:write("\t\t"..tostring(entityPositionsV.name[0]):lower().."\n") break end end else - io.write ("\t\t".."-1".."\n") + file:write("\t\t-1\n") end elseif event:getType() == df.history_event_type.CREATE_ENTITY_POSITION and k == "position" then local entity = findEntity(event.site_civ) if (entity ~= nil and v > -1) then for entitypositionsK, entityPositionsV in ipairs(entity.positions.own) do if entityPositionsV.id == v then - io.write ("\t\t"..""..tostring(entityPositionsV.name[0]):lower().."".."\n") + file:write("\t\t"..tostring(entityPositionsV.name[0]):lower().."\n") break end end else - io.write ("\t\t".."-1".."\n") + file:write("\t\t-1\n") end elseif event:getType() == df.history_event_type.REMOVE_HF_ENTITY_LINK and k == "link_type" then - io.write ("\t\t".."<"..k..">"..df.histfig_entity_link_type[v]:lower().."".."\n") + file:write("\t\t<"..k..">"..df.histfig_entity_link_type[v]:lower().."\n") elseif event:getType() == df.history_event_type.REMOVE_HF_ENTITY_LINK and k == "position_id" then local entity = findEntity(event.civ) if (entity ~= nil and event.civ > -1 and v > -1) then for entitypositionsK, entityPositionsV in ipairs(entity.positions.own) do if entityPositionsV.id == v then - io.write ("\t\t"..""..tostring(entityPositionsV.name[0]):lower().."".."\n") + file:write("\t\t"..tostring(entityPositionsV.name[0]):lower().."\n") break end end else - io.write ("\t\t".."-1".."\n") + file:write("\t\t-1\n") end elseif event:getType() == df.history_event_type.ADD_HF_HF_LINK and k == "type" then - io.write ("\t\t"..""..df.histfig_hf_link_type[v]:lower().."".."\n") + file:write("\t\t"..df.histfig_hf_link_type[v]:lower().."\n") elseif event:getType() == df.history_event_type.ADD_HF_SITE_LINK and k == "type" then - io.write ("\t\t"..""..df.histfig_site_link_type[v]:lower().."".."\n") + file:write("\t\t"..df.histfig_site_link_type[v]:lower().."\n") elseif event:getType() == df.history_event_type.REMOVE_HF_SITE_LINK and k == "type" then - io.write ("\t\t"..""..df.histfig_site_link_type[v]:lower().."".."\n") + file:write("\t\t"..df.histfig_site_link_type[v]:lower().."\n") elseif (event:getType() == df.history_event_type.ITEM_STOLEN or event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM or event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM_IMPROVEMENT ) and k == "item_type" then - io.write ("\t\t"..""..df.item_type[v]:lower().."".."\n") + file:write("\t\t"..df.item_type[v]:lower().."\n") elseif (event:getType() == df.history_event_type.ITEM_STOLEN or event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM or event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM_IMPROVEMENT ) and k == "item_subtype" then --if event.item_type > -1 and v > -1 then - io.write ("\t\t".."<"..k..">"..getItemSubTypeName(event.item_type,v).."".."\n") + file:write("\t\t<"..k..">"..getItemSubTypeName(event.item_type,v).."\n") --end elseif event:getType() == df.history_event_type.ITEM_STOLEN and k == "mattype" then if (v > -1) then if (dfhack.matinfo.decode(event.mattype, event.matindex) == nil) then - io.write ("\t\t"..""..event.mattype.."".."\n") - io.write ("\t\t"..""..event.matindex.."".."\n") + file:write("\t\t"..event.mattype.."\n") + file:write("\t\t"..event.matindex.."\n") else - io.write ("\t\t"..""..dfhack.matinfo.toString(dfhack.matinfo.decode(event.mattype, event.matindex)).."".."\n") + file:write("\t\t"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.mattype, event.matindex)).."\n") end end elseif (event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM or @@ -357,19 +359,19 @@ function export_more_legends_xml() ) and k == "mat_type" then if (v > -1) then if (dfhack.matinfo.decode(event.mat_type, event.mat_index) == nil) then - io.write ("\t\t"..""..event.mat_type.."".."\n") - io.write ("\t\t"..""..event.mat_index.."".."\n") + file:write("\t\t"..event.mat_type.."\n") + file:write("\t\t"..event.mat_index.."\n") else - io.write ("\t\t"..""..dfhack.matinfo.toString(dfhack.matinfo.decode(event.mat_type, event.mat_index)).."".."\n") + file:write("\t\t"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.mat_type, event.mat_index)).."\n") end end elseif event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM_IMPROVEMENT and k == "imp_mat_type" then if (v > -1) then if (dfhack.matinfo.decode(event.imp_mat_type, event.imp_mat_index) == nil) then - io.write ("\t\t"..""..event.imp_mat_type.."".."\n") - io.write ("\t\t"..""..event.imp_mat_index.."".."\n") + file:write("\t\t"..event.imp_mat_type.."\n") + file:write("\t\t"..event.imp_mat_index.."\n") else - io.write ("\t\t"..""..dfhack.matinfo.toString(dfhack.matinfo.decode(event.imp_mat_type, event.imp_mat_index)).."".."\n") + file:write("\t\t"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.imp_mat_type, event.imp_mat_index)).."\n") end end @@ -389,76 +391,76 @@ function export_more_legends_xml() event:getType() == df.history_event_type.TOPICAGREEMENT_REJECTED or event:getType() == df.history_event_type.TOPICAGREEMENT_MADE ) and k == "topic" then - io.write ("\t\t"..""..tostring(df.meeting_topic[v]):lower().."".."\n") + file:write("\t\t"..tostring(df.meeting_topic[v]):lower().."\n") elseif event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM_IMPROVEMENT and k == "improvement_type" then - io.write ("\t\t"..""..df.improvement_type[v]:lower().."".."\n") + file:write("\t\t"..df.improvement_type[v]:lower().."\n") elseif ((event:getType() == df.history_event_type.HIST_FIGURE_REACH_SUMMIT and k == "figures") or (event:getType() == df.history_event_type.HIST_FIGURE_NEW_PET and k == "group") or (event:getType() == df.history_event_type.BODY_ABUSED and k == "bodies")) then for detailK,detailV in pairs(v) do - io.write ("\t\t".."<"..k..">"..detailV.."".."\n") + file:write("\t\t<"..k..">"..detailV.."\n") end elseif event:getType() == df.history_event_type.HIST_FIGURE_NEW_PET and k == "pets" then for detailK,detailV in pairs(v) do - io.write ("\t\t".."<"..k..">"..(df.global.world.raws.creatures.all[detailV].creature_id):lower().."".."\n") + file:write("\t\t<"..k..">"..(df.global.world.raws.creatures.all[detailV].creature_id):lower().."\n") end elseif event:getType() == df.history_event_type.BODY_ABUSED and (k == "props") then - io.write ("\t\t".."<"..k.."_item_type"..">"..tostring(df.item_type[event.props.item.item_type]):lower().."".."\n") - io.write ("\t\t".."<"..k.."_item_subtype"..">"..getItemSubTypeName(event.props.item.item_type,event.props.item.item_subtype).."".."\n") + file:write("\t\t<"..k.."_item_type>"..tostring(df.item_type[event.props.item.item_type]):lower().."\n") + file:write("\t\t<"..k.."_item_subtype>"..getItemSubTypeName(event.props.item.item_type,event.props.item.item_subtype).."\n") if (event.props.item.mat_type > -1) then if (dfhack.matinfo.decode(event.props.item.mat_type, event.props.item.mat_index) == nil) then - io.write ("\t\t"..""..event.props.item.mat_type.."".."\n") - io.write ("\t\t"..""..event.props.item.mat_index.."".."\n") + file:write("\t\t"..event.props.item.mat_type.."\n") + file:write("\t\t"..event.props.item.mat_index.."\n") else - io.write ("\t\t"..""..dfhack.matinfo.toString(dfhack.matinfo.decode(event.props.item.mat_type, event.props.item.mat_index)).."".."\n") + file:write("\t\t"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.props.item.mat_type, event.props.item.mat_index)).."\n") end end - --io.write ("\t\t".."<"..k.."_item_mat_type"..">"..tostring(event.props.item.mat_type).."".."\n") - --io.write ("\t\t".."<"..k.."_item_mat_index"..">"..tostring(event.props.item.mat_index).."".."\n") - io.write ("\t\t".."<"..k.."_pile_type"..">"..tostring(event.props.pile_type).."".."\n") + --file:write("\t\t<"..k.."_item_mat_type>"..tostring(event.props.item.mat_type).."\n") + --file:write("\t\t<"..k.."_item_mat_index>"..tostring(event.props.item.mat_index).."\n") + file:write("\t\t<"..k.."_pile_type>"..tostring(event.props.pile_type).."\n") elseif event:getType() == df.history_event_type.ASSUME_IDENTITY and k == "identity" then if (table.contains(df.global.world.identities.all,v)) then if (df.global.world.identities.all[v].histfig_id == -1) then local thisIdentity = df.global.world.identities.all[v] - io.write ("\t\t"..""..thisIdentity.name.first_name.."".."\n") - io.write ("\t\t"..""..(df.global.world.raws.creatures.all[thisIdentity.race].creature_id):lower().."".."\n") - io.write ("\t\t"..""..(df.global.world.raws.creatures.all[thisIdentity.race].caste[thisIdentity.caste].caste_id):lower().."".."\n") + file:write("\t\t"..thisIdentity.name.first_name.."\n") + file:write("\t\t"..(df.global.world.raws.creatures.all[thisIdentity.race].creature_id):lower().."\n") + file:write("\t\t"..(df.global.world.raws.creatures.all[thisIdentity.race].caste[thisIdentity.caste].caste_id):lower().."\n") else - io.write ("\t\t"..""..df.global.world.identities.all[v].histfig_id.."".."\n") + file:write("\t\t"..df.global.world.identities.all[v].histfig_id.."\n") end end elseif event:getType() == df.history_event_type.MASTERPIECE_CREATED_ARCH_CONSTRUCT and k == "building_type" then - io.write ("\t\t"..""..df.building_type[v]:lower().."".."\n") + file:write("\t\t"..df.building_type[v]:lower().."\n") elseif event:getType() == df.history_event_type.MASTERPIECE_CREATED_ARCH_CONSTRUCT and k == "building_subtype" then if (df.building_type[event.building_type]:lower() == "furnace") then - io.write ("\t\t"..""..df.furnace_type[v]:lower().."".."\n") + file:write("\t\t"..df.furnace_type[v]:lower().."\n") elseif v > -1 then - io.write ("\t\t"..""..tostring(v).."".."\n") + file:write("\t\t"..tostring(v).."\n") end elseif k == "race" then if v > -1 then - io.write ("\t\t"..""..(df.global.world.raws.creatures.all[v].creature_id):lower().."".."\n") + file:write("\t\t"..(df.global.world.raws.creatures.all[v].creature_id):lower().."\n") end elseif k == "caste" then if v > -1 then - io.write ("\t\t"..""..(df.global.world.raws.creatures.all[event.race].caste[v].caste_id):lower().."".."\n") + file:write("\t\t"..(df.global.world.raws.creatures.all[event.race].caste[v].caste_id):lower().."\n") end elseif k == "interaction" and event:getType() == df.history_event_type.HF_DOES_INTERACTION then - io.write ("\t\t"..""..df.global.world.raws.interactions[v].str[3].value.."".."\n") - io.write ("\t\t"..""..df.global.world.raws.interactions[v].str[4].value.."".."\n") + file:write("\t\t"..df.global.world.raws.interactions[v].str[3].value.."\n") + file:write("\t\t"..df.global.world.raws.interactions[v].str[4].value.."\n") elseif k == "interaction" and event:getType() == df.history_event_type.HF_LEARNS_SECRET then - io.write ("\t\t"..""..df.global.world.raws.interactions[v].str[2].value.."".."\n") + file:write("\t\t"..df.global.world.raws.interactions[v].str[2].value.."\n") elseif event:getType() == df.history_event_type.HIST_FIGURE_DIED and k == "weapon" then for detailK,detailV in pairs(v) do if (detailK == "item") then if detailV > -1 then - io.write ("\t\t".."<"..detailK..">"..detailV.."".."\n") + file:write("\t\t<"..detailK..">"..detailV.."\n") local thisItem = df.item.find(detailV) if (thisItem ~= nil) then if (thisItem.flags.artifact == true) then for refk,refv in pairs(thisItem.general_refs) do if (refv:getType() == 1) then - io.write ("\t\t"..""..refv.artifact_id.."".."\n") + file:write("\t\t"..refv.artifact_id.."\n") break end end @@ -468,27 +470,27 @@ function export_more_legends_xml() end elseif (detailK == "item_type") then if event.weapon.item > -1 then - io.write ("\t\t".."<"..detailK..">"..tostring(df.item_type[detailV]):lower().."".."\n") + file:write("\t\t<"..detailK..">"..tostring(df.item_type[detailV]):lower().."\n") end elseif (detailK == "item_subtype") then if event.weapon.item > -1 and detailV > -1 then - io.write ("\t\t".."<"..detailK..">"..getItemSubTypeName(event.weapon.item_type,detailV).."".."\n") + file:write("\t\t<"..detailK..">"..getItemSubTypeName(event.weapon.item_type,detailV).."\n") end elseif (detailK == "mattype") then if (detailV > -1) then - io.write ("\t\t"..""..dfhack.matinfo.toString(dfhack.matinfo.decode(event.weapon.mattype, event.weapon.matindex)).."".."\n") + file:write("\t\t"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.weapon.mattype, event.weapon.matindex)).."\n") end elseif (detailK == "matindex") then elseif (detailK == "shooter_item") then if detailV > -1 then - io.write ("\t\t".."<"..detailK..">"..detailV.."".."\n") + file:write("\t\t<"..detailK..">"..detailV.."\n") local thisItem = df.item.find(detailV) if thisItem ~= nil then if (thisItem.flags.artifact == true) then for refk,refv in pairs(thisItem.general_refs) do if (refv:getType() == 1) then - io.write ("\t\t"..""..refv.artifact_id.."".."\n") + file:write("\t\t"..refv.artifact_id.."\n") break end end @@ -497,42 +499,42 @@ function export_more_legends_xml() end elseif (detailK == "shooter_item_type") then if event.weapon.shooter_item > -1 then - io.write ("\t\t".."<"..detailK..">"..tostring(df.item_type[detailV]):lower().."".."\n") + file:write("\t\t<"..detailK..">"..tostring(df.item_type[detailV]):lower().."\n") end elseif (detailK == "shooter_item_subtype") then if event.weapon.shooter_item > -1 and detailV > -1 then - io.write ("\t\t".."<"..detailK..">"..getItemSubTypeName(event.weapon.shooter_item_type,detailV).."".."\n") + file:write("\t\t<"..detailK..">"..getItemSubTypeName(event.weapon.shooter_item_type,detailV).."\n") end elseif (detailK == "shooter_mattype") then if (detailV > -1) then - io.write ("\t\t"..""..dfhack.matinfo.toString(dfhack.matinfo.decode(event.weapon.shooter_mattype, event.weapon.shooter_matindex)).."".."\n") + file:write("\t\t"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.weapon.shooter_mattype, event.weapon.shooter_matindex)).."\n") end elseif (detailK == "shooter_matindex") then --skip elseif detailK == "slayer_race" or detailK == "slayer_caste" then --skip else - io.write ("\t\t".."<"..detailK..">"..detailV.."".."\n") + file:write("\t\t<"..detailK..">"..detailV.."\n") end end elseif event:getType() == df.history_event_type.HIST_FIGURE_DIED and k == "death_cause" then - io.write ("\t\t".."<"..k..">"..df.death_type[v]:lower().."".."\n") + file:write("\t\t<"..k..">"..df.death_type[v]:lower().."\n") elseif event:getType() == df.history_event_type.CHANGE_HF_JOB and (k == "new_job" or k == "old_job") then - io.write ("\t\t".."<"..k..">"..df.profession[v]:lower().."".."\n") + file:write("\t\t<"..k..">"..df.profession[v]:lower().."\n") else - io.write ("\t\t".."<"..k..">"..tostring(v).."".."\n") + file:write("\t\t<"..k..">"..tostring(v).."\n") end end - io.write ("\t".."".."\n") + file:write("\t\n") end end - io.write ("".."\n") - io.write ("".."\n") - io.write ("".."\n") - io.write ("".."\n") - io.write ("".."\n") - io.write ("".."\n") - io.close() + file:write("\n") + file:write("\n") + file:write("\n") + file:write("\n") + file:write("\n") + file:write("\n") + file:close() end -- export information and XML ('p, x') From d775333c005c85210189af82368c260576b9994a Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 31 Dec 2015 13:38:38 -0500 Subject: [PATCH 30/60] fix-unit-occupancy: Account for wagon occupancy correctly Previously, non-central wagon tiles would have their occupancy flag cleared. --- plugins/fix-unit-occupancy.cpp | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/plugins/fix-unit-occupancy.cpp b/plugins/fix-unit-occupancy.cpp index e83392512..8b7be0c0f 100644 --- a/plugins/fix-unit-occupancy.cpp +++ b/plugins/fix-unit-occupancy.cpp @@ -9,6 +9,7 @@ #include "modules/Translation.h" #include "modules/World.h" +#include "df/creature_raw.h" #include "df/map_block.h" #include "df/unit.h" #include "df/world.h" @@ -40,16 +41,6 @@ static std::string get_unit_description(df::unit *unit) return desc; } -df::unit *findUnit(int x, int y, int z) -{ - for (auto u = world->units.active.begin(); u != world->units.active.end(); ++u) - { - if ((**u).pos.x == x && (**u).pos.y == y && (**u).pos.z == z) - return *u; - } - return NULL; -} - struct uo_buf { uint32_t dim_x, dim_y, dim_z; size_t size; @@ -154,8 +145,21 @@ unsigned fix_unit_occupancy (color_ostream &out, uo_opts &opts) } } - for (auto u = world->units.active.begin(); u != world->units.active.end(); ++u) - uo_buffer.set((**u).pos.x, (**u).pos.y, (**u).pos.z, 0); + for (auto it = world->units.active.begin(); it != world->units.active.end(); ++it) + { + df::unit *u = *it; + if (!u || u->flags1.bits.caged || u->pos.x < 0) + continue; + df::creature_raw *craw = df::creature_raw::find(u->race); + int unit_extents = (craw && craw->flags.is_set(df::creature_raw_flags::EQUIPMENT_WAGON)) ? 1 : 0; + for (int16_t x = u->pos.x - unit_extents; x <= u->pos.x + unit_extents; ++x) + { + for (int16_t y = u->pos.y - unit_extents; y <= u->pos.y + unit_extents; ++y) + { + uo_buffer.set(x, y, u->pos.z, 0); + } + } + } for (size_t i = 0; i < uo_buffer.size; i++) { From 090787e9272efc3866b797e9b102a6b175672da4 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 31 Dec 2015 13:41:46 -0500 Subject: [PATCH 31/60] Update xml --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 357197817..3b7d07599 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 357197817bf05677ff51287b3015e1621f4c0888 +Subproject commit 3b7d075992692c709730d34720b35598b5cb4710 From 31745f4a361d5300fd10a3387e4dd2e7f3da5abd Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 31 Dec 2015 14:32:31 -0500 Subject: [PATCH 32/60] Fix a couple exportlegends issues and update xml --- library/xml | 2 +- scripts/exportlegends.lua | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/library/xml b/library/xml index 3b7d07599..bd5f8a901 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 3b7d075992692c709730d34720b35598b5cb4710 +Subproject commit bd5f8a90178371f83ef6348eaa3e376aee5e7af3 diff --git a/scripts/exportlegends.lua b/scripts/exportlegends.lua index 39b21e417..b8c37efde 100644 --- a/scripts/exportlegends.lua +++ b/scripts/exportlegends.lua @@ -211,8 +211,8 @@ function export_more_legends_xml() file:write("\t\t"..(df.global.world.raws.creatures.all[entityV.race].creature_id):lower().."\n") end file:write("\t\t"..(df.historical_entity_type[entityV.type]):lower().."\n") - if (df.historical_entity_type[entityV.type]):lower() == "religion" then -- Get worshipped figure - if (entityV.unknown1b ~= nil and entityV.unknown1b.worship ~= nill and + if entityV.type == df.historical_entity_type.Religion then -- Get worshipped figure + if (entityV.unknown1b ~= nil and entityV.unknown1b.worship ~= nil and #entityV.unknown1b.worship == 1) then file:write("\t\t"..entityV.unknown1b.worship[0].."\n") else From ef62c044a321361ba5fab3998c167ec826b807bf Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 1 Jan 2016 10:30:02 -0500 Subject: [PATCH 33/60] Update NEWS, xml --- NEWS.rst | 2 +- library/xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS.rst b/NEWS.rst index d43229487..f42a52d5a 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -60,7 +60,7 @@ New Features Fixes ----- -- `exportlegends`: Handles entities without specific races +- `exportlegends`: Handles entities without specific races, and a few other fixes for things new to v0.42 - `showmood`: Fixed name display on OS X/Linux diff --git a/library/xml b/library/xml index bd5f8a901..9b14c4560 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit bd5f8a90178371f83ef6348eaa3e376aee5e7af3 +Subproject commit 9b14c45604fcd1ba5851d91d122d75f86936000b From d0c28d3f50ba9178462681114dcaef74addc15b1 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 1 Jan 2016 11:15:29 -0500 Subject: [PATCH 34/60] Prevent plugins with active viewscreens from being unloaded This requires plugins to pass plugin_self to Screen::show(), but avoids the need to implement special checks in plugin_onstatechange for the SC_BEGIN_UNLOAD event. --- NEWS.rst | 2 ++ library/PluginManager.cpp | 7 +++++++ library/include/modules/Screen.h | 6 +++++- library/modules/Screen.cpp | 26 +++++++++++++++++++++++++- plugins/autochop.cpp | 4 ++-- plugins/buildingplan.cpp | 2 +- plugins/command-prompt.cpp | 4 +--- plugins/dwarfmonitor.cpp | 8 ++++---- plugins/embark-tools.cpp | 16 ++++------------ plugins/hotkeys.cpp | 2 +- plugins/manipulator.cpp | 10 +++++----- plugins/stocks.cpp | 12 ++++-------- 12 files changed, 61 insertions(+), 38 deletions(-) diff --git a/NEWS.rst b/NEWS.rst index f42a52d5a..17ea64e38 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -41,6 +41,8 @@ Internals ./dfhack +devel/print-args example "Dwarf Fortress.exe" +devel/print-args example +- Prevented plugins with active viewscreens from being unloaded and causing a crash + New Scripts ----------- - `devel/save-version`: Displays DF version information about the current save diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index 2dad07797..9711e2e4d 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -24,6 +24,7 @@ distribution. #include "modules/EventManager.h" #include "modules/Filesystem.h" +#include "modules/Screen.h" #include "Internal.h" #include "Core.h" #include "MemAccess.h" @@ -385,6 +386,12 @@ bool Plugin::unload(color_ostream &con) // if we are actually loaded if(state == PS_LOADED) { + if (Screen::hasActiveScreens(this)) + { + con.printerr("Cannot unload plugin %s: has active viewscreens\n", name.c_str()); + access->unlock(); + return false; + } EventManager::unregisterAll(this); // notify the plugin about an attempt to shutdown if (plugin_onstatechange && diff --git a/library/include/modules/Screen.h b/library/include/modules/Screen.h index 37dc9808c..2c4a29062 100644 --- a/library/include/modules/Screen.h +++ b/library/include/modules/Screen.h @@ -54,6 +54,7 @@ namespace df namespace DFHack { class Core; + class Plugin; typedef std::set interface_key_set; @@ -208,9 +209,12 @@ namespace DFHack DFHACK_EXPORT bool findGraphicsTile(const std::string &page, int x, int y, int *ptile, int *pgs = NULL); // Push and remove viewscreens - DFHACK_EXPORT bool show(df::viewscreen *screen, df::viewscreen *before = NULL); + DFHACK_EXPORT bool show(df::viewscreen *screen, df::viewscreen *before = NULL, Plugin *p = NULL); + inline bool show(df::viewscreen *screen, Plugin *p) + { return show(screen, NULL, p); } DFHACK_EXPORT void dismiss(df::viewscreen *screen, bool to_first = false); DFHACK_EXPORT bool isDismissed(df::viewscreen *screen); + DFHACK_EXPORT bool hasActiveScreens(Plugin *p); /// Retrieve the string representation of the bound key. DFHACK_EXPORT std::string getKeyDisplay(df::interface_key key); diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index 0f79e1a46..71103ffda 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -282,7 +282,9 @@ bool Screen::findGraphicsTile(const std::string &pagename, int x, int y, int *pt return false; } -bool Screen::show(df::viewscreen *screen, df::viewscreen *before) +static std::map plugin_screens; + +bool Screen::show(df::viewscreen *screen, df::viewscreen *before, Plugin *plugin) { CHECK_NULL_POINTER(screen); CHECK_INVALID_ARGUMENT(!screen->parent && !screen->child); @@ -306,6 +308,9 @@ bool Screen::show(df::viewscreen *screen, df::viewscreen *before) if (dfhack_viewscreen::is_instance(screen)) static_cast(screen)->onShow(); + if (plugin) + plugin_screens[screen] = plugin; + return true; } @@ -313,6 +318,10 @@ void Screen::dismiss(df::viewscreen *screen, bool to_first) { CHECK_NULL_POINTER(screen); + auto it = plugin_screens.find(screen); + if (it != plugin_screens.end()) + plugin_screens.erase(it); + if (screen->breakdown_level != interface_breakdown_types::NONE) return; @@ -332,6 +341,21 @@ bool Screen::isDismissed(df::viewscreen *screen) return screen->breakdown_level != interface_breakdown_types::NONE; } +bool Screen::hasActiveScreens(Plugin *plugin) +{ + if (plugin_screens.empty()) + return false; + df::viewscreen *screen = &gview->view; + while (screen) + { + auto it = plugin_screens.find(screen); + if (it != plugin_screens.end() && it->second == plugin) + return true; + screen = screen->child; + } + return false; +} + #ifdef _LINUX // Link to the libgraphics class directly: class DFHACK_EXPORT enabler_inputst { diff --git a/plugins/autochop.cpp b/plugins/autochop.cpp index f7ba3d300..cbb5db0b3 100644 --- a/plugins/autochop.cpp +++ b/plugins/autochop.cpp @@ -600,7 +600,7 @@ struct autochop_hook : public df::viewscreen_dwarfmodest if (isInDesignationMenu() && input->count(interface_key::CUSTOM_C)) { sendKey(interface_key::LEAVESCREEN); - Screen::show(new ViewscreenAutochop()); + Screen::show(new ViewscreenAutochop(), plugin_self); } else { @@ -643,7 +643,7 @@ command_result df_autochop (color_ostream &out, vector & parameters) return CR_WRONG_USAGE; } if (Maps::IsValid()) - Screen::show(new ViewscreenAutochop()); + Screen::show(new ViewscreenAutochop(), plugin_self); return CR_OK; } diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index ddc804897..6f833ba6d 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -161,7 +161,7 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest } else if (input->count(interface_key::CUSTOM_SHIFT_M)) { - Screen::show(new ViewscreenChooseMaterial(planner.getDefaultItemFilterForType(type))); + Screen::show(new ViewscreenChooseMaterial(planner.getDefaultItemFilterForType(type)), plugin_self); } else if (input->count(interface_key::CUSTOM_SHIFT_Q)) { diff --git a/plugins/command-prompt.cpp b/plugins/command-prompt.cpp index dd3b59398..042d5f642 100644 --- a/plugins/command-prompt.cpp +++ b/plugins/command-prompt.cpp @@ -318,7 +318,7 @@ command_result show_prompt(color_ostream &out, std::vector & param std::string params; for(size_t i=0;icount(interface_key::CUSTOM_SHIFT_Z)) { @@ -1665,7 +1665,7 @@ private: static void open_stats_srceen() { - Screen::show(new ViewscreenFortStats()); + Screen::show(new ViewscreenFortStats(), plugin_self); } static void add_work_history(df::unit *unit, activity_type type) @@ -1915,12 +1915,12 @@ static command_result dwarfmonitor_cmd(color_ostream &out, vector & par else if (cmd == 's' || cmd == 'S') { if(Maps::IsValid()) - Screen::show(new ViewscreenFortStats()); + Screen::show(new ViewscreenFortStats(), plugin_self); } else if (cmd == 'p' || cmd == 'P') { if(Maps::IsValid()) - Screen::show(new ViewscreenPreferences()); + Screen::show(new ViewscreenPreferences(), plugin_self); } else if (cmd == 'r' || cmd == 'R') { diff --git a/plugins/embark-tools.cpp b/plugins/embark-tools.cpp index 7fa9cafca..d2bdf4909 100644 --- a/plugins/embark-tools.cpp +++ b/plugins/embark-tools.cpp @@ -21,6 +21,9 @@ using namespace DFHack; using df::global::enabler; using df::global::gps; +DFHACK_PLUGIN("embark-tools"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); + #define FOR_ITER_TOOLS(iter) for(auto iter = tools.begin(); iter != tools.end(); iter++) void update_embark_sidebar (df::viewscreen_choose_start_sitest * screen) @@ -684,7 +687,7 @@ struct choose_start_site_hook : df::viewscreen_choose_start_sitest void display_settings() { - Screen::show(new embark_tools_settings); + Screen::show(new embark_tools_settings, plugin_self); } inline bool is_valid_page() @@ -734,9 +737,6 @@ struct choose_start_site_hook : df::viewscreen_choose_start_sitest IMPLEMENT_VMETHOD_INTERPOSE(choose_start_site_hook, feed); IMPLEMENT_VMETHOD_INTERPOSE(choose_start_site_hook, render); -DFHACK_PLUGIN("embark-tools"); -DFHACK_PLUGIN_IS_ENABLED(is_enabled); - command_result embark_tools_cmd (color_ostream &out, std::vector & parameters); DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) @@ -783,14 +783,6 @@ DFhackCExport command_result plugin_enable (color_ostream &out, bool enable) DFhackCExport command_result plugin_onstatechange (color_ostream &out, state_change_event evt) { - if (evt == SC_BEGIN_UNLOAD) - { - if (Gui::getCurFocus() == "dfhack/embark-tools/options") - { - out.printerr("Settings screen active.\n"); - return CR_FAILURE; - } - } return CR_OK; } diff --git a/plugins/hotkeys.cpp b/plugins/hotkeys.cpp index 45ca07cbd..d7139dd96 100644 --- a/plugins/hotkeys.cpp +++ b/plugins/hotkeys.cpp @@ -319,7 +319,7 @@ static command_result hotkeys_cmd(color_ostream &out, vector & paramete if (Gui::getFocusString(top_screen) != "dfhack/viewscreen_hotkeys") { find_active_keybindings(top_screen); - Screen::show(new ViewscreenHotkeys(top_screen)); + Screen::show(new ViewscreenHotkeys(top_screen), plugin_self); } } } diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index a3f0f8ef8..e2a4e54a0 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -1773,14 +1773,14 @@ void viewscreen_unitlaborsst::feed(set *events) if (events->count(interface_key::CUSTOM_B)) { - Screen::show(new viewscreen_unitbatchopst(units, true, &do_refresh_names)); + Screen::show(new viewscreen_unitbatchopst(units, true, &do_refresh_names), plugin_self); } if (events->count(interface_key::CUSTOM_E)) { vector tmp; tmp.push_back(cur); - Screen::show(new viewscreen_unitbatchopst(tmp, false, &do_refresh_names)); + Screen::show(new viewscreen_unitbatchopst(tmp, false, &do_refresh_names), plugin_self); } if (events->count(interface_key::CUSTOM_P)) @@ -1791,11 +1791,11 @@ void viewscreen_unitlaborsst::feed(set *events) has_selected = true; if (has_selected) { - Screen::show(new viewscreen_unitprofessionset(units, true)); + Screen::show(new viewscreen_unitprofessionset(units, true), plugin_self); } else { vector tmp; tmp.push_back(cur); - Screen::show(new viewscreen_unitprofessionset(tmp, false)); + Screen::show(new viewscreen_unitprofessionset(tmp, false), plugin_self); } } @@ -2144,7 +2144,7 @@ struct unitlist_hook : df::viewscreen_unitlistst { if (units[page].size()) { - Screen::show(new viewscreen_unitlaborsst(units[page], cursor_pos[page])); + Screen::show(new viewscreen_unitlaborsst(units[page], cursor_pos[page]), plugin_self); return; } } diff --git a/plugins/stocks.cpp b/plugins/stocks.cpp index 85fedb9f2..8565c703f 100644 --- a/plugins/stocks.cpp +++ b/plugins/stocks.cpp @@ -771,7 +771,7 @@ public: } else if (input->count(interface_key::HELP)) { - Screen::show(new search_help); + Screen::show(new search_help, plugin_self); } bool key_processed = false; @@ -1425,7 +1425,7 @@ struct stocks_hook : public df::viewscreen_storesst if (input->count(interface_key::CUSTOM_E)) { Screen::dismiss(this); - Screen::show(new ViewscreenStocks()); + Screen::show(new ViewscreenStocks(), plugin_self); return; } INTERPOSE_NEXT(feed)(input); @@ -1457,7 +1457,7 @@ struct stocks_stockpile_hook : public df::viewscreen_dwarfmodest if (input->count(interface_key::CUSTOM_I)) { - Screen::show(new ViewscreenStocks(sp)); + Screen::show(new ViewscreenStocks(sp), plugin_self); return true; } @@ -1531,7 +1531,7 @@ static command_result stocks_cmd(color_ostream &out, vector & parameter } else if (toLower(parameters[0])[0] == 's') { - Screen::show(new ViewscreenStocks()); + Screen::show(new ViewscreenStocks(), plugin_self); return CR_OK; } } @@ -1557,10 +1557,6 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan case SC_MAP_LOADED: ViewscreenStocks::reset(); break; - case SC_BEGIN_UNLOAD: - if (Gui::getCurFocus().find("dfhack/stocks") == 0) - return CR_FAILURE; - break; default: break; } From 93fe222c352136cab2e549b0876f3dc409cb1aa5 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 1 Jan 2016 11:20:54 -0500 Subject: [PATCH 35/60] Revert #719 No plugins built prior to the symbol naming change in 4fc6cb6f can be loaded in DFHack for v0.42. --- library/PluginManager.cpp | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index 9711e2e4d..774d19143 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -273,23 +273,13 @@ bool Plugin::load(color_ostream &con) plugin_abort_load; \ return false; \ } - #define plugin_check_symbols(sym1,sym2) \ - if (!LookupPlugin(plug, sym1) && !LookupPlugin(plug, sym2)) \ - { \ - con.printerr("Plugin %s: missing symbols: %s & %s\n", name.c_str(), sym1, sym2); \ - plugin_abort_load; \ - return false; \ - } - plugin_check_symbols("plugin_name", "name") // allow r3 plugins - plugin_check_symbols("plugin_version", "version") // allow r3 plugins + plugin_check_symbol("plugin_name") + plugin_check_symbol("plugin_version") plugin_check_symbol("plugin_self") plugin_check_symbol("plugin_init") plugin_check_symbol("plugin_globals") const char ** plug_name =(const char ** ) LookupPlugin(plug, "plugin_name"); - if (!plug_name) // allow r3 plugin naming - plug_name = (const char ** )LookupPlugin(plug, "name"); - if (name != *plug_name) { con.printerr("Plugin %s: name mismatch, claims to be %s\n", name.c_str(), *plug_name); @@ -297,9 +287,6 @@ bool Plugin::load(color_ostream &con) return false; } const char ** plug_version =(const char ** ) LookupPlugin(plug, "plugin_version"); - if (!plug_version) // allow r3 plugin version - plug_version =(const char ** ) LookupPlugin(plug, "version"); - const char ** plug_git_desc_ptr = (const char**) LookupPlugin(plug, "plugin_git_description"); Plugin **plug_self = (Plugin**)LookupPlugin(plug, "plugin_self"); const char *dfhack_version = Version::dfhack_version(); From d92363368968e3ac6dcede3b10ea9ddc9ceef8af Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 1 Jan 2016 11:34:52 -0500 Subject: [PATCH 36/60] Add moversti, TheBloke, txtsd to Authors.rst --- docs/Authors.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/Authors.rst b/docs/Authors.rst index 13ef59721..0b2066076 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -58,6 +58,7 @@ Mike Stewart thewonderidiot Mikko Juola Noeda Adeon MithrilTuxedo MithrilTuxedo mizipzor mizipzor +moversti moversti Neil Little nmlittle Nick Rart nickrart comestible Omniclasm @@ -91,7 +92,9 @@ Simon Jackson sizeak Tacomagic Tim Walberg twalberg Timothy Collett danaris +Tom Jobbins TheBloke Tom Prince +txtsd txtsd U-glouglou\\simon Valentin Ochs Cat-Ion Vjek From 81b055ee936d2abb0a51b29266ec4d41ebb1a4c2 Mon Sep 17 00:00:00 2001 From: PeridexisErrant Date: Fri, 4 Dec 2015 15:48:15 +0930 Subject: [PATCH 37/60] Implement "weather" as a script --- NEWS.rst | 4 ++ docs/Plugins.rst | 5 -- plugins/CMakeLists.txt | 1 - plugins/weather.cpp | 147 ----------------------------------------- scripts/weather.lua | 35 ++++++++++ 5 files changed, 39 insertions(+), 153 deletions(-) delete mode 100644 plugins/weather.cpp create mode 100644 scripts/weather.lua diff --git a/NEWS.rst b/NEWS.rst index 17ea64e38..a30f91c1a 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -65,6 +65,10 @@ Fixes - `exportlegends`: Handles entities without specific races, and a few other fixes for things new to v0.42 - `showmood`: Fixed name display on OS X/Linux +Misc Improvements +----------------- +- `weather`: now implemented by a script + DFHack 0.40.24-r5 ================= diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 952813180..c50001dbd 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -2105,11 +2105,6 @@ Options: Beware that filling in hollow veins will trigger a demon invasion on top of your miner when you dig into the region that used to be hollow. -weather -======= -Prints the current weather, and lets you change the weather to 'clear', 'rain' -or 'snow', with those words as commands (eg ``weather rain``). - ================= diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 78e13b157..9781401ff 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -167,7 +167,6 @@ if (BUILD_SUPPORTED) # DFHACK_PLUGIN(treefarm treefarm.cpp) DFHACK_PLUGIN(tubefill tubefill.cpp) add_subdirectory(tweak) - DFHACK_PLUGIN(weather weather.cpp) DFHACK_PLUGIN(workflow workflow.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(workNow workNow.cpp) DFHACK_PLUGIN(zone zone.cpp LINK_LIBRARIES lua) diff --git a/plugins/weather.cpp b/plugins/weather.cpp deleted file mode 100644 index 481c1a779..000000000 --- a/plugins/weather.cpp +++ /dev/null @@ -1,147 +0,0 @@ -#include "Core.h" -#include "Console.h" -#include "Export.h" -#include "PluginManager.h" -#include -#include -#include "modules/World.h" -#include "DataDefs.h" -#include "df/weather_type.h" - -using std::vector; -using std::string; -using namespace DFHack; -using namespace df::enums; - -DFHACK_PLUGIN("weather"); - -REQUIRE_GLOBAL(current_weather); - -bool locked = false; -unsigned char locked_data[25]; - -command_result weather (color_ostream &out, vector & parameters); - -DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) -{ - commands.push_back(PluginCommand( - "weather", "Print the weather map or change weather.", - weather, false, - " Prints the current weather map by default.\n" - "Options:\n" - " snow - make it snow everywhere.\n" - " rain - make it rain.\n" - " clear - clear the sky.\n" - )); - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ - return CR_OK; -} - -command_result weather (color_ostream &con, vector & parameters) -{ - int val_override = -1; - bool lock = false; - bool unlock = false; - bool snow = false; - bool rain = false; - bool clear = false; - for(size_t i = 0; i < parameters.size();i++) - { - if(parameters[i] == "rain") - rain = true; - else if(parameters[i] == "snow") - snow = true; - else if(parameters[i] == "clear") - clear = true; - else if(parameters[i] == "lock") - lock = true; - else if(parameters[i] == "unlock") - unlock = true; - else - { - val_override = atoi(parameters[i].c_str()); - if(val_override == 0) - return CR_WRONG_USAGE; - } - } - if(lock && unlock) - { - con << "Lock or unlock? DECIDE!" << std::endl; - return CR_FAILURE; - } - int cnt = 0; - cnt += rain; - cnt += snow; - cnt += clear; - if(cnt > 1) - { - con << "Rain, snow or clear sky? DECIDE!" << std::endl; - return CR_FAILURE; - } - bool something = lock || unlock || rain || snow || clear || val_override != -1; - - CoreSuspender suspend; - - if(!current_weather) - { - con << "Weather support seems broken :(" << std::endl; - return CR_FAILURE; - } - if(!something) - { - // paint weather map - con << "Weather map (C = clear, R = rain, S = snow):" << std::endl; - for(int y = 0; y<5;y++) - { - for(int x = 0; x<5;x++) - { - switch((*current_weather)[x][y]) - { - case weather_type::None: - con << "C "; - break; - case weather_type::Rain: - con << "R "; - break; - case weather_type::Snow: - con << "S "; - break; - default: - con << (int) (*current_weather)[x][y] << " "; - break; - } - } - con << std::endl; - } - } - else - { - // weather changing action! - if(rain) - { - con << "Here comes the rain." << std::endl; - World::SetCurrentWeather(weather_type::Rain); - } - if(snow) - { - con << "Snow everywhere!" << std::endl; - World::SetCurrentWeather(weather_type::Snow); - } - if(clear) - { - con << "Suddenly, sunny weather!" << std::endl; - World::SetCurrentWeather(weather_type::None); - } - if(val_override != -1) - { - con << "I have no damn idea what this is... " << val_override << std::endl; - World::SetCurrentWeather(val_override); - } - // FIXME: weather lock needs map ID to work reliably... needs to be implemented. - } - return CR_OK; -} diff --git a/scripts/weather.lua b/scripts/weather.lua new file mode 100644 index 000000000..2cbb05fa8 --- /dev/null +++ b/scripts/weather.lua @@ -0,0 +1,35 @@ +-- Print the weather map or change weather. +local helpstr = [[=begin + +weather +======= +Prints a map of the local weather, or with arguments ``clear``, +``rain``, and ``snow`` changes the weather. + +=end]] + +local args = {...} +if args[1] == "help" or args[1] == "?" then + print("The current weather is "..df.weather_type[dfhack.world.ReadCurrentWeather()]) + print((helpstr:gsub('=[a-z]+', ''))) +elseif args[1] == "clear" then + dfhack.world.SetCurrentWeather(df.weather_type["None"]) + print("The weather has cleared.") +elseif args[1] == "rain" then + dfhack.world.SetCurrentWeather(df.weather_type["Rain"]) + print("It is now raining.") +elseif args[1] == "snow" then + dfhack.world.SetCurrentWeather(df.weather_type["Snow"]) + print("It is now snowing.") +else + -- df.global.current_weather is arranged in columns, not rows + kind = {[0]="C ", "R ", "S "} + print("Weather map (C = clear, R = rain, S = snow):") + for y=0, 4 do + s = "" + for x=0, 4 do + s = s..kind[df.global.current_weather[x][y]] + end + print(s) + end +end From 51690b4ba264fa5e44642c5912728a38e0131949 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 1 Jan 2016 11:46:40 -0500 Subject: [PATCH 38/60] weather: re-add val_override, minor cleanup --- scripts/weather.lua | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/scripts/weather.lua b/scripts/weather.lua index 2cbb05fa8..a61ab48bd 100644 --- a/scripts/weather.lua +++ b/scripts/weather.lua @@ -9,26 +9,37 @@ Prints a map of the local weather, or with arguments ``clear``, =end]] local args = {...} -if args[1] == "help" or args[1] == "?" then +local cmd +local val_override = tonumber(args[1]) +if args[1] then + cmd = args[1]:sub(1, 1) +end +if cmd == "h" or cmd == "?" then print("The current weather is "..df.weather_type[dfhack.world.ReadCurrentWeather()]) print((helpstr:gsub('=[a-z]+', ''))) -elseif args[1] == "clear" then - dfhack.world.SetCurrentWeather(df.weather_type["None"]) +elseif cmd == "c" then + dfhack.world.SetCurrentWeather(df.weather_type.None) print("The weather has cleared.") -elseif args[1] == "rain" then - dfhack.world.SetCurrentWeather(df.weather_type["Rain"]) +elseif cmd == "r" then + dfhack.world.SetCurrentWeather(df.weather_type.Rain) print("It is now raining.") -elseif args[1] == "snow" then - dfhack.world.SetCurrentWeather(df.weather_type["Snow"]) +elseif cmd == "s" then + dfhack.world.SetCurrentWeather(df.weather_type.Snow) print("It is now snowing.") +elseif val_override then + dfhack.world.SetCurrentWeather(val_override) + print("Set weather to " .. val_override) +elseif args[1] then + qerror("Unrecognized argument: " .. args[1]) else -- df.global.current_weather is arranged in columns, not rows - kind = {[0]="C ", "R ", "S "} + kind = {[0]="C", "R", "S"} print("Weather map (C = clear, R = rain, S = snow):") for y=0, 4 do s = "" for x=0, 4 do - s = s..kind[df.global.current_weather[x][y]] + local cur = df.global.current_weather[x][y] + s = s .. (kind[cur] or cur) .. ' ' end print(s) end From 9e020bb8c11e3088ba0f923e07516f232771dac9 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 1 Jan 2016 11:50:11 -0500 Subject: [PATCH 39/60] Make title-version show git info for dev builds --- plugins/title-version.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/title-version.cpp b/plugins/title-version.cpp index c62568f1e..78a694211 100644 --- a/plugins/title-version.cpp +++ b/plugins/title-version.cpp @@ -34,7 +34,12 @@ struct title_version_hook : df::viewscreen_titlest { int x = 0, y = 0; OutputString(COLOR_WHITE, x, y, string("DFHack ") + DFHACK_VERSION); if (!DFHACK_IS_RELEASE) + { OutputString(COLOR_WHITE, x, y, " (dev)"); + x = 0; y = 1; + OutputString(COLOR_WHITE, x, y, "Git: "); + OutputString(COLOR_WHITE, x, y, DFHACK_GIT_DESCRIPTION); + } } }; From 45ba3c0c68522bd218b850eb29d4b4a523048eaf Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 1 Jan 2016 20:01:24 -0500 Subject: [PATCH 40/60] Try specifying ZLIB_ROOT --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a20b8dba..193339077 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -171,6 +171,7 @@ endif() if(NOT UNIX) SET(ZLIB_ROOT depends/zlib/) endif() +set(ZLIB_ROOT /usr/lib/i386-linux-gnu) find_package(ZLIB REQUIRED) include_directories(depends/protobuf) include_directories(depends/lua/include) From 06b3b94e8de1ccf3e3c99809da55fd7f98460557 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 2 Jan 2016 17:06:30 -0500 Subject: [PATCH 41/60] Tweak prerelease warning - low risk of save corruption at this point --- scripts/gui/prerelease-warning.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/gui/prerelease-warning.lua b/scripts/gui/prerelease-warning.lua index 8f99e450d..7eb6094c3 100644 --- a/scripts/gui/prerelease-warning.lua +++ b/scripts/gui/prerelease-warning.lua @@ -20,7 +20,7 @@ local utils = require 'utils' message = { 'This is a prerelease build of DFHack. Some structures are likely', NEWLINE, 'to be incorrect, resulting in crashes or save corruption', NEWLINE, - {pen=COLOR_LIGHTRED, text='Make backups of your saves and avoid saving if possible.'}, + {pen=COLOR_LIGHTRED, text='Make backups of your saves often!'}, } pack_message = pack_message or [[ From 2acdede03bde2f486479099787d7a4a09b7f3c00 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 4 Jan 2016 19:29:30 -0500 Subject: [PATCH 42/60] New tweak: hide-priority Adds an option to hide designation priority indicators --- NEWS.rst | 1 + dfhack.init-example | 2 + docs/Plugins.rst | 1 + plugins/tweak/tweak.cpp | 6 +++ plugins/tweak/tweaks/hide-priority.h | 61 ++++++++++++++++++++++++++++ 5 files changed, 71 insertions(+) create mode 100644 plugins/tweak/tweaks/hide-priority.h diff --git a/NEWS.rst b/NEWS.rst index a30f91c1a..b1c214f57 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -58,6 +58,7 @@ New Features - `tweak`: + - ``tweak hide-priority``: Adds an option to hide designation priority indicators - ``tweak title-start-rename``: Adds a safe rename option to the title screen "Start Playing" menu Fixes diff --git a/dfhack.init-example b/dfhack.init-example index 080bb1488..d1a28e169 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -181,10 +181,12 @@ tweak import-priority-category # Misc. UI tweaks tweak civ-view-agreement tweak fps-min +tweak hide-priority tweak kitchen-keys tweak kitchen-prefs-empty tweak max-wheelbarrow tweak shift-8-scroll +tweak title-start-rename tweak tradereq-pet-gender ########################### diff --git a/docs/Plugins.rst b/docs/Plugins.rst index c50001dbd..ef6a004ad 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -294,6 +294,7 @@ Subcommands that persist until disabled or DF quits: :fast-trade: Makes Shift-Down in the Move Goods to Depot and Trade screens select the current item (fully, in case of a stack), and scroll down one line. :fps-min: Fixes the in-game minimum FPS setting +:hide-priority: Adds an option to hide designation priority indicators :import-priority-category: Allows changing the priority of all goods in a category when discussing an import agreement with the liaison diff --git a/plugins/tweak/tweak.cpp b/plugins/tweak/tweak.cpp index 6b7f11603..df472e84d 100644 --- a/plugins/tweak/tweak.cpp +++ b/plugins/tweak/tweak.cpp @@ -87,6 +87,7 @@ #include "tweaks/fast-heat.h" #include "tweaks/fast-trade.h" #include "tweaks/fps-min.h" +#include "tweaks/hide-priority.h" #include "tweaks/import-priority-category.h" #include "tweaks/kitchen-keys.h" #include "tweaks/kitchen-prefs-color.h" @@ -195,6 +196,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector main.mode) + { + case df::ui_sidebar_mode::DesignateMine: + case df::ui_sidebar_mode::DesignateRemoveRamps: + case df::ui_sidebar_mode::DesignateUpStair: + case df::ui_sidebar_mode::DesignateDownStair: + case df::ui_sidebar_mode::DesignateUpDownStair: + case df::ui_sidebar_mode::DesignateUpRamp: + case df::ui_sidebar_mode::DesignateChannel: + case df::ui_sidebar_mode::DesignateGatherPlants: + case df::ui_sidebar_mode::DesignateRemoveDesignation: + case df::ui_sidebar_mode::DesignateSmooth: + case df::ui_sidebar_mode::DesignateCarveTrack: + case df::ui_sidebar_mode::DesignateEngrave: + case df::ui_sidebar_mode::DesignateCarveFortification: + case df::ui_sidebar_mode::DesignateChopTrees: + case df::ui_sidebar_mode::DesignateToggleEngravings: + case df::ui_sidebar_mode::DesignateToggleMarker: + case df::ui_sidebar_mode::DesignateRemoveConstruction: + return true; + default: + return false; + } + } + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + INTERPOSE_NEXT(render)(); + if (valid_mode()) + { + auto dims = Gui::getDwarfmodeViewDims(); + if (dims.menu_on) + { + int x = dims.menu_x1 + 1, y = gps->dimy - (gps->dimy > 26 ? 8 : 7); + OutputToggleString(x, y, "Show priorities", df::interface_key::CUSTOM_ALT_P, + ui_sidebar_menus->designation.priority_set, true, 0, + COLOR_WHITE, COLOR_LIGHTRED); + } + } + } + DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set *input)) + { + if (valid_mode() && input->count(df::interface_key::CUSTOM_ALT_P)) + ui_sidebar_menus->designation.priority_set = !ui_sidebar_menus->designation.priority_set; + else + INTERPOSE_NEXT(feed)(input); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(hide_priority_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(hide_priority_hook, render); From bdd4b11e2d1ee67ea4439c1e7208d8c73845f27c Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 4 Jan 2016 19:30:10 -0500 Subject: [PATCH 43/60] Add an option to scan for all "standing_orders_*" globals --- scripts/devel/find-offsets.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/devel/find-offsets.lua b/scripts/devel/find-offsets.lua index 368e8ac98..a2b5a55df 100644 --- a/scripts/devel/find-offsets.lua +++ b/scripts/devel/find-offsets.lua @@ -1623,6 +1623,9 @@ local function exec_finder_so(gname, seq, _depends) end depends[k] = v end + if force_scan['standing_orders'] then + force_scan[gname] = true + end exec_finder(function() return find_standing_orders(gname, seq, depends) end, gname) From 3bcd58fca7181a4293b96b08f695f1065b1d48a4 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 4 Jan 2016 19:31:57 -0500 Subject: [PATCH 44/60] Use consistent key display --- plugins/mousequery.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mousequery.cpp b/plugins/mousequery.cpp index 3731d96c0..2da47d8cd 100644 --- a/plugins/mousequery.cpp +++ b/plugins/mousequery.cpp @@ -582,7 +582,7 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest { int x = left_margin; int y = gps->dimy - 2; - OutputToggleString(x, y, "Box Select", "Alt+M", box_designation_enabled, + OutputToggleString(x, y, "Box Select", interface_key::CUSTOM_ALT_M, box_designation_enabled, true, left_margin, COLOR_WHITE, COLOR_LIGHTRED); } From aa435a14d70c7c6656976a6e39fbc66d8fe35ea4 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 4 Jan 2016 19:55:12 -0500 Subject: [PATCH 45/60] find-offsets: Look up colors from colors.txt if possible --- scripts/devel/find-offsets.lua | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/scripts/devel/find-offsets.lua b/scripts/devel/find-offsets.lua index a2b5a55df..00cd4e076 100644 --- a/scripts/devel/find-offsets.lua +++ b/scripts/devel/find-offsets.lua @@ -465,6 +465,22 @@ end -- enabler -- +local function lookup_colors() + local f = io.open('data/init/colors.txt', 'r') or error('failed to open file') + local text = f:read('*all') + f:close() + local colors = {} + for _, color in pairs({'BLACK', 'BLUE', 'GREEN', 'CYAN', 'RED', 'MAGENTA', + 'BROWN', 'LGRAY', 'DGRAY', 'LBLUE', 'LGREEN', 'LCYAN', 'LRED', + 'LMAGENTA', 'YELLOW', 'WHITE'}) do + for _, part in pairs({'R', 'G', 'B'}) do + local opt = color .. '_' .. part + table.insert(colors, tonumber(text:match(opt .. ':(%d+)') or error('missing from colors.txt: ' .. opt))) + end + end + return colors +end + local function is_valid_enabler(e) if not ms.is_valid_vector(e.textures.raws, 4) or not ms.is_valid_vector(e.text_system, 4) @@ -478,7 +494,7 @@ end local function find_enabler() -- Data from data/init/colors.txt - local colors = { + local default_colors = { 0, 0, 0, 0, 0, 128, 0, 128, 0, 0, 128, 128, 128, 0, 0, 128, 0, 128, 128, 128, 0, 192, 192, 192, 128, 128, 128, @@ -486,10 +502,21 @@ local function find_enabler() 255, 0, 0, 255, 0, 255, 255, 255, 0, 255, 255, 255 } + local colors + local ok, ret = pcall(lookup_colors) + if not ok then + dfhack.printerr('Failed to look up colors, using defaults: \n' .. ret) + colors = default_colors + else + colors = ret + end for i = 1,#colors do colors[i] = colors[i]/255 end local idx, addr = data.float:find_one(colors) + if not idx then + idx, addr = data.float:find_one(default_colors) + end if idx then validate_offset('enabler', is_valid_enabler, addr, df.enabler, 'ccolor') return From 9ae55177ef12b940a5e0b52446e89c27fecd8ce4 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 4 Jan 2016 20:05:45 -0500 Subject: [PATCH 46/60] Stop feed_menu_choice exit sequence from triggering after restarting scan --- scripts/devel/find-offsets.lua | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/scripts/devel/find-offsets.lua b/scripts/devel/find-offsets.lua index 00cd4e076..ce475e8f1 100644 --- a/scripts/devel/find-offsets.lua +++ b/scripts/devel/find-offsets.lua @@ -260,17 +260,14 @@ local function dwarfmode_to_top() end local function feed_menu_choice(catnames,catkeys,enum,enter_seq,exit_seq,prompt) - local entered = false return function (idx) if idx == 0 and prompt and not utils.prompt_yes_no(' Proceed?', true) then return false end - idx = idx % #catnames + 1 - if not entered then - entered = true - else + if idx > 0 then dwarfmode_feed_input(table.unpack(exit_seq or {})) end + idx = idx % #catnames + 1 dwarfmode_feed_input(table.unpack(enter_seq or {})) dwarfmode_feed_input(catkeys[idx]) if enum then From 7eb442e98c88e019a8aa41156122875f1ddc01ac Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 4 Jan 2016 20:09:05 -0500 Subject: [PATCH 47/60] Feed D_ORDERS instead of relying on sidebar mode enum --- scripts/devel/find-offsets.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/devel/find-offsets.lua b/scripts/devel/find-offsets.lua index ce475e8f1..89cb0918a 100644 --- a/scripts/devel/find-offsets.lua +++ b/scripts/devel/find-offsets.lua @@ -1626,7 +1626,7 @@ local function find_standing_orders(gname, seq, depends) 'Auto-searching for ' .. gname, 'uint8_t', function(idx) - df.global.ui.main.mode = df.ui_sidebar_mode.Orders + dwarfmode_feed_input('D_ORDERS') dwarfmode_feed_input(table.unpack(seq)) return true end From da3c6404fa026e776d4a495fe585161947d0ee4d Mon Sep 17 00:00:00 2001 From: Nikolay Amiantov Date: Wed, 6 Jan 2016 03:03:19 +0300 Subject: [PATCH 48/60] blueprint: fix compilation (i.e. undefined find) --- plugins/blueprint.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/blueprint.cpp b/plugins/blueprint.cpp index b9047a14e..badc094c9 100644 --- a/plugins/blueprint.cpp +++ b/plugins/blueprint.cpp @@ -2,6 +2,8 @@ //By cdombroski //Translates a region of tiles specified by the cursor and arguments/prompts into a series of blueprint files suitable for digfort/buildingplan/quickfort +#include + #include #include From 97fb50c28f8a7a65ee1b3b70d7a1d5a7411261f3 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 6 Jan 2016 17:30:07 -0500 Subject: [PATCH 49/60] Add better support for default ListColumn values This is only necessary for multiple-selection columns, and there aren't any with non-pointer entry types currently, but casting nullptr to non-pointer types isn't a great idea. --- plugins/listcolumn.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/listcolumn.h b/plugins/listcolumn.h index 0576ca4e4..5c96b4594 100644 --- a/plugins/listcolumn.h +++ b/plugins/listcolumn.h @@ -35,8 +35,9 @@ public: bool allow_search; bool feed_mouse_set_highlight; bool feed_changed_highlight; + T default_value; - ListColumn() + ListColumn(const T default_value_ = T()) { bottom_margin = 3; clear(); @@ -50,6 +51,7 @@ public: allow_search = true; feed_mouse_set_highlight = false; feed_changed_highlight = false; + default_value = default_value_; } void clear() @@ -310,7 +312,7 @@ public: { vector results = getSelectedElems(true); if (results.size() == 0) - return (T)nullptr; + return default_value; else return results[0]; } From 203b9aca2a6c76cc552b9e32abb2459053ed21e0 Mon Sep 17 00:00:00 2001 From: Jim Lisi Date: Fri, 8 Jan 2016 14:53:33 -0500 Subject: [PATCH 50/60] autolabor-artisans.lua remove brewer skill alcohol has no quality level, so brewer skill has no effect on output quality --- scripts/autolabor-artisans.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/autolabor-artisans.lua b/scripts/autolabor-artisans.lua index 22d394646..68b85817c 100644 --- a/scripts/autolabor-artisans.lua +++ b/scripts/autolabor-artisans.lua @@ -14,7 +14,6 @@ local artisan_labors = { "ARCHITECT", "ANIMALTRAIN", "LEATHER", - "BREWER", "WEAVER", "CLOTHESMAKER", "COOK", From 974b427833e033b1ad012ed892c7d0a0ab63bbb0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 8 Jan 2016 17:01:22 -0500 Subject: [PATCH 51/60] Add stonetoad to Authors.rst --- docs/Authors.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Authors.rst b/docs/Authors.rst index 0b2066076..92ed1a9d4 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -37,6 +37,7 @@ IndigoFenix James Logsdon jlogsdon Japa JapaMala Jared Adams +Jim Lisi stonetoad jj jjyg jj`` John Beisley huin John Shade gsvslto From d670ee8ab4de69c2ba57aec703b7735d0ed3d407 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 8 Jan 2016 19:27:11 -0500 Subject: [PATCH 52/60] New tweak: block-labors Prevents labors that can't be used from being toggled Suggested at http://www.bay12forums.com/smf/index.php?topic=121451.msg6719464#msg6719464 --- NEWS.rst | 1 + dfhack.init-example | 1 + docs/Plugins.rst | 1 + library/xml | 2 +- plugins/tweak/tweak.cpp | 8 +++ plugins/tweak/tweaks/block-labors.h | 104 ++++++++++++++++++++++++++++ 6 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 plugins/tweak/tweaks/block-labors.h diff --git a/NEWS.rst b/NEWS.rst index b1c214f57..504237163 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -58,6 +58,7 @@ New Features - `tweak`: + - ``tweak block-labors``: Prevents labors that can't be used from being toggled - ``tweak hide-priority``: Adds an option to hide designation priority indicators - ``tweak title-start-rename``: Adds a safe rename option to the title screen "Start Playing" menu diff --git a/dfhack.init-example b/dfhack.init-example index d1a28e169..430aa803a 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -179,6 +179,7 @@ tweak farm-plot-select tweak import-priority-category # Misc. UI tweaks +tweak block-labors # Prevents labors that can't be used from being toggled tweak civ-view-agreement tweak fps-min tweak hide-priority diff --git a/docs/Plugins.rst b/docs/Plugins.rst index ef6a004ad..eb519cac6 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -280,6 +280,7 @@ Subcommands that persist until disabled or DF quits: in advmode. The issue is that the screen tries to force you to select the contents separately from the container. This forcefully skips child reagents. +:block-labors: Prevents labors that can't be used from being toggled :civ-view-agreement: Fixes overlapping text on the "view agreement" screen :craft-age-wear: Fixes the behavior of crafted items wearing out over time (:bug:`6003`). With this tweak, items made from cloth and leather will gain a level of diff --git a/library/xml b/library/xml index 9b14c4560..d952e79b6 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 9b14c45604fcd1ba5851d91d122d75f86936000b +Subproject commit d952e79b684f518b04eae5f973fa39679a2a4fa0 diff --git a/plugins/tweak/tweak.cpp b/plugins/tweak/tweak.cpp index df472e84d..3b17d9fa0 100644 --- a/plugins/tweak/tweak.cpp +++ b/plugins/tweak/tweak.cpp @@ -79,6 +79,7 @@ #include "tweaks/adamantine-cloth-wear.h" #include "tweaks/advmode-contained.h" +#include "tweaks/block-labors.h" #include "tweaks/civ-agreement-ui.h" #include "tweaks/craft-age-wear.h" #include "tweaks/eggs-fertile.h" @@ -117,7 +118,9 @@ REQUIRE_GLOBAL(ui_area_map_width); REQUIRE_GLOBAL(ui_build_selector); REQUIRE_GLOBAL(ui_building_item_cursor); REQUIRE_GLOBAL(ui_menu_width); +REQUIRE_GLOBAL(ui_look_cursor); REQUIRE_GLOBAL(ui_sidebar_menus); +REQUIRE_GLOBAL(ui_unit_view_mode); REQUIRE_GLOBAL(ui_workshop_in_add); REQUIRE_GLOBAL(world); @@ -177,6 +180,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector main.mode == df::ui_sidebar_mode::ViewUnits && + ui_unit_view_mode->value == df::ui_unit_view_mode::T_value::PrefLabor; + } + + inline bool forbidden_labor (df::unit *unit, df::unit_labor labor) + { + return is_valid_enum_item(labor) && !Units::isValidLabor(unit, labor); + } + + inline bool all_labors_enabled (df::unit *unit, df::unit_labor_category cat) + { + FOR_ENUM_ITEMS(unit_labor, labor) + { + if (ENUM_ATTR(unit_labor, category, labor) == cat && + !unit->status.labors[labor] && + !forbidden_labor(unit, labor)) + return false; + } + return true; + } + + inline void recolor_line (int x1, int x2, int y, UIColor color) + { + for (int x = x1; x <= x2; x++) + { + auto tile = Screen::readTile(x, y); + tile.fg = color; + tile.bold = false; + Screen::paintTile(tile, x, y); + } + } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + INTERPOSE_NEXT(render)(); + auto dims = Gui::getDwarfmodeViewDims(); + if (valid_mode()) + { + df::unit *unit = Gui::getAnyUnit(this); + + for (int y = 5, i = (*ui_look_cursor/13)*13; + y <= 17 && i < unit_labors_sidemenu.size(); + ++y, ++i) + { + df::unit_labor labor = unit_labors_sidemenu[i]; + df::unit_labor_category cat = df::unit_labor_category(labor); + + if (is_valid_enum_item(cat) && all_labors_enabled(unit, cat)) + recolor_line(dims.menu_x1, dims.menu_x2, y, COLOR_WHITE); + + if (forbidden_labor(unit, labor)) + recolor_line(dims.menu_x1, dims.menu_x2, y, COLOR_RED + + (unit->status.labors[labor] ? 8 : 0)); + } + } + } + DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set *input)) + { + using namespace df::enums::interface_key; + if (valid_mode()) + { + df::unit *unit = Gui::getAnyUnit(this); + df::unit_labor labor = unit_labors_sidemenu[*ui_look_cursor]; + df::unit_labor_category cat = df::unit_labor_category(labor); + + if ((input->count(SELECT) || input->count(SELECT_ALL)) && forbidden_labor(unit, labor)) + { + unit->status.labors[labor] = false; + return; + } + else if (input->count(SELECT_ALL) && is_valid_enum_item(cat)) + { + bool new_state = !all_labors_enabled(unit, cat); + FOR_ENUM_ITEMS(unit_labor, labor) + { + if (ENUM_ATTR(unit_labor, category, labor) == cat) + unit->status.labors[labor] = (new_state && !forbidden_labor(unit, labor)); + } + return; + } + } + INTERPOSE_NEXT(feed)(input); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(block_labors_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(block_labors_hook, render); From 8cec8af3b14230d5bb9bd60b58b732029c6d3a0c Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 8 Jan 2016 21:08:26 -0500 Subject: [PATCH 53/60] CMake: Support cross-compiling This relies on a working cross-compiler, of course, as well as a separate build directory for cross-compiled builds, with DFHACK_NATIVE_BUILD_DIR pointing to an already-successful native build directory (this is needed for protoc). --- CMakeLists.txt | 5 +++++ depends/protobuf/CMakeLists.txt | 19 ++++++++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 193339077..a8e29ebb5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,6 +58,11 @@ if(MSVC) add_definitions( "/wd4819" ) endif() +IF(CMAKE_CROSSCOMPILING) + SET(DFHACK_NATIVE_BUILD_DIR "DFHACK_NATIVE_BUILD_DIR-NOTFOUND" CACHE FILEPATH "Path to a native build directory") + INCLUDE("${DFHACK_NATIVE_BUILD_DIR}/ImportExecutables.cmake") +ENDIF() + # set up folder structures for IDE solutions # MSVC Express won't load solutions that use this. It also doesn't include MFC supported # Check for MFC! diff --git a/depends/protobuf/CMakeLists.txt b/depends/protobuf/CMakeLists.txt index 9ec13e6ca..39726c81a 100644 --- a/depends/protobuf/CMakeLists.txt +++ b/depends/protobuf/CMakeLists.txt @@ -22,7 +22,12 @@ IF(CMAKE_COMPILER_IS_GNUCC) FOREACH(namespace std::tr1 std ) IF(HAVE_HASH_MAP EQUAL 0 AND NOT STL_HASH_OLD_GCC) CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/testHashMap.cpp.in" "${CMAKE_CURRENT_SOURCE_DIR}/testHashMap.cpp") - TRY_RUN(HASH_MAP_RUN_RESULT HASH_MAP_COMPILE_RESULT ${PROJECT_BINARY_DIR}/CMakeTmp "${CMAKE_CURRENT_SOURCE_DIR}/testHashMap.cpp") + IF(CMAKE_CROSSCOMPILING) + TRY_COMPILE(HASH_MAP_COMPILE_RESULT ${PROJECT_BINARY_DIR}/CMakeTmp "${CMAKE_CURRENT_SOURCE_DIR}/testHashMap.cpp") + SET(HASH_MAP_RUN_RESULT ${HASH_MAP_COMPILE_RESULT}) + ELSE() + TRY_RUN(HASH_MAP_RUN_RESULT HASH_MAP_COMPILE_RESULT ${PROJECT_BINARY_DIR}/CMakeTmp "${CMAKE_CURRENT_SOURCE_DIR}/testHashMap.cpp") + ENDIF() IF (HASH_MAP_COMPILE_RESULT AND HASH_MAP_RUN_RESULT EQUAL 1) SET(HASH_MAP_H <${header}>) STRING(REPLACE "map" "set" HASH_SET_H ${HASH_MAP_H}) @@ -237,8 +242,12 @@ TARGET_LINK_LIBRARIES(protoc protobuf) # Protobuf compiler executable -ADD_EXECUTABLE(protoc-bin google/protobuf/compiler/main.cc google/protobuf/compiler/command_line_interface.h google/protobuf/compiler/cpp/cpp_generator.h) -IDE_FOLDER(protoc-bin "Depends") +IF(NOT CMAKE_CROSSCOMPILING) + ADD_EXECUTABLE(protoc-bin google/protobuf/compiler/main.cc google/protobuf/compiler/command_line_interface.h google/protobuf/compiler/cpp/cpp_generator.h) + IDE_FOLDER(protoc-bin "Depends") -SET_TARGET_PROPERTIES(protoc-bin PROPERTIES OUTPUT_NAME protoc) -TARGET_LINK_LIBRARIES(protoc-bin protoc) + SET_TARGET_PROPERTIES(protoc-bin PROPERTIES OUTPUT_NAME protoc) + TARGET_LINK_LIBRARIES(protoc-bin protoc) + + EXPORT(TARGETS protoc-bin FILE ${CMAKE_BINARY_DIR}/ImportExecutables.cmake ) +ENDIF() From 53780d8232546107a745051a630289e45a6daa98 Mon Sep 17 00:00:00 2001 From: Eric Wald Date: Fri, 8 Jan 2016 22:28:07 -0700 Subject: [PATCH 54/60] New autogems plugin, automatically cutting rough gems. --- NEWS.rst | 4 + dfhack.init-example | 1 + docs/Plugins.rst | 7 + plugins/CMakeLists.txt | 1 + plugins/autogems.cpp | 293 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 306 insertions(+) create mode 100644 plugins/autogems.cpp diff --git a/NEWS.rst b/NEWS.rst index 504237163..065efeee7 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -43,6 +43,10 @@ Internals - Prevented plugins with active viewscreens from being unloaded and causing a crash +New Plugins +----------- +- `autogems`: Creates a new Workshop Order setting, automatically cutting rough gems + New Scripts ----------- - `devel/save-version`: Displays DF version information about the current save diff --git a/dfhack.init-example b/dfhack.init-example index 430aa803a..f2cf6b156 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -211,6 +211,7 @@ enable \ confirm \ dwarfmonitor \ mousequery \ + autogems \ automelt \ autotrade \ buildingplan \ diff --git a/docs/Plugins.rst b/docs/Plugins.rst index eb519cac6..33db4d8c3 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -875,6 +875,13 @@ job-duplicate In :kbd:`q` mode, when a job is highlighted within a workshop or furnace building, calling ``job-duplicate`` instantly duplicates the job. +.. _autogems: + +autogems +======== +Creates a new Workshop Order setting, automatically cutting rough gems +when `enabled `. + .. _stockflow: stockflow diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 9781401ff..a321f178a 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -90,6 +90,7 @@ if (BUILD_SUPPORTED) # DFHACK_PLUGIN(advtools advtools.cpp) DFHACK_PLUGIN(autochop autochop.cpp) DFHACK_PLUGIN(autodump autodump.cpp) + DFHACK_PLUGIN(autogems autogems.cpp) DFHACK_PLUGIN(autohauler autohauler.cpp) DFHACK_PLUGIN(autolabor autolabor.cpp) DFHACK_PLUGIN(automaterial automaterial.cpp) diff --git a/plugins/autogems.cpp b/plugins/autogems.cpp new file mode 100644 index 000000000..4ae6b7efc --- /dev/null +++ b/plugins/autogems.cpp @@ -0,0 +1,293 @@ +/* + * Autogems plugin. + * Creates a new Workshop Order setting, automatically cutting rough gems. + * For best effect, include "enable autogems" in your dfhack.init configuration. + */ + +#include "uicommon.h" + +#include "modules/Buildings.h" +#include "modules/Gui.h" +#include "modules/Job.h" +#include "modules/World.h" + +#include "df/building_workshopst.h" +#include "df/buildings_other_id.h" +#include "df/builtin_mats.h" +#include "df/general_ref_building_holderst.h" +#include "df/job.h" +#include "df/job_item.h" +#include "df/viewscreen_dwarfmodest.h" + +#define CONFIG_KEY "autogems/config" +#define DELTA_TICKS 1800 +#define MAX_WORKSHOP_JOBS 10 + +using namespace DFHack; + +DFHACK_PLUGIN("autogems"); +DFHACK_PLUGIN_IS_ENABLED(enabled); + +REQUIRE_GLOBAL(ui); +REQUIRE_GLOBAL(world); + +typedef decltype(df::item::id) item_id; +typedef decltype(df::job_item::mat_index) mat_index; +typedef std::map gem_map; + +bool running = false; +decltype(world->frame_counter) last_frame_count = 0; +const char *tagline = "Creates a new Workshop Order setting, automatically cutting rough gems."; +const char *usage = ( + " enable autogems\n" + " Enable the plugin.\n" + " disable autogems\n" + " Disable the plugin.\n" + "\n" + "While enabled, the Current Workshop Orders screen (o-W) have a new option:\n" + " g: Auto Cut Gems\n" + "\n" + "While this option is enabled, jobs will be created in Jeweler's Workshops\n" + "to cut any accessible rough gems.\n" +); + +void add_task(mat_index gem_type, df::building_workshopst *workshop) { + // Create a single task in the specified workshop. + // Partly copied from Buildings::linkForConstruct(); perhaps a refactor is in order. + + auto ref = df::allocate(); + if (!ref) { + //Core::printerr("Could not allocate general_ref_building_holderst\n"); + return; + } + + ref->building_id = workshop->id; + + auto item = new df::job_item(); + if (!item) { + //Core::printerr("Could not allocate job_item\n"); + return; + } + + item->item_type = df::item_type::ROUGH; + item->mat_type = df::builtin_mats::INORGANIC; + item->mat_index = gem_type; + item->quantity = 1; + item->vector_id = df::job_item_vector_id::ROUGH; + + auto job = new df::job(); + if (!job) { + //Core::printerr("Could not allocate job\n"); + return; + } + + job->job_type = df::job_type::CutGems; + job->pos = df::coord(workshop->centerx, workshop->centery, workshop->z); + job->mat_type = df::builtin_mats::INORGANIC; + job->mat_index = gem_type; + job->general_refs.push_back(ref); + job->job_items.push_back(item); + + workshop->jobs.push_back(job); + Job::linkIntoWorld(job); +} + +void add_tasks(gem_map &gem_types, df::building_workshopst *workshop) { + int slots = MAX_WORKSHOP_JOBS - workshop->jobs.size(); + if (slots <= 0) { + return; + } + + for (auto g = gem_types.begin(); g != gem_types.end() && slots > 0; ++g) { + while (g->second > 0 && slots > 0) { + add_task(g->first, workshop); + g->second -= 1; + slots -= 1; + } + } +} + +void create_jobs() { + // Creates jobs in Jeweler's Workshops as necessary. + // Todo: Consider path availability? + std::set stockpiled; + std::set unlinked; + gem_map available; + auto workshops = &world->buildings.other[df::buildings_other_id::WORKSHOP_JEWELER]; + + for (auto w = workshops->begin(); w != workshops->end(); ++w) { + auto workshop = virtual_cast(*w); + auto links = workshop->links.take_from_pile; + + if (workshop->construction_stage < 3) { + // Construction in progress. + continue; + } + + if (workshop->jobs.size() == 1 && workshop->jobs[0]->job_type == df::job_type::DestroyBuilding) { + // Queued for destruction. + continue; + } + + if (links.size() > 0) { + for (auto l = links.begin(); l != links.end() && workshop->jobs.size() <= MAX_WORKSHOP_JOBS; ++l) { + auto stockpile = virtual_cast(*l); + gem_map piled; + + Buildings::StockpileIterator stored; + for (stored.begin(stockpile); !stored.done(); ++stored) { + auto item = *stored; + if (item->getType() == item_type::ROUGH && item->getMaterial() == builtin_mats::INORGANIC) { + stockpiled.insert(item->id); + piled[item->getMaterialIndex()] += 1; + } + } + + // Decrement current jobs from all linked workshops, not just this one. + auto outbound = stockpile->links.give_to_workshop; + for (auto ws = outbound.begin(); ws != outbound.end(); ++ws) { + auto shop = virtual_cast(*ws); + for (auto j = shop->jobs.begin(); j != shop->jobs.end(); ++j) { + auto job = *j; + if (job->job_type == df::job_type::CutGems) { + if (job->flags.bits.repeat) { + piled[job->mat_index] = 0; + } else { + piled[job->mat_index] -= 1; + } + } + } + } + + add_tasks(piled, workshop); + } + } else { + // Note which gem types have already been ordered to be cut. + for (auto j = workshop->jobs.begin(); j != workshop->jobs.end(); ++j) { + auto job = *j; + if (job->job_type == df::job_type::CutGems) { + available[job->mat_index] -= job->flags.bits.repeat? 100: 1; + } + } + + if (workshop->jobs.size() <= MAX_WORKSHOP_JOBS) { + unlinked.insert(workshop); + } + } + } + + if (unlinked.size() > 0) { + // Count how many gems of each type are available to be cut. + // Gems in stockpiles linked to specific workshops don't count. + auto gems = world->items.other[items_other_id::ROUGH]; + for (auto g = gems.begin(); g != gems.end(); ++g) { + auto item = *g; + // ROUGH also includes raw glass; the INORGANIC check filters that out. + if (item->getMaterial() == builtin_mats::INORGANIC && !stockpiled.count(item->id)) { + available[item->getMaterialIndex()] += 1; + } + } + + for (auto w = unlinked.begin(); w != unlinked.end(); ++w) { + add_tasks(available, *w); + } + } +} + +DFhackCExport command_result plugin_onupdate(color_ostream &out) { + if (running && (world->frame_counter - last_frame_count >= DELTA_TICKS)) { + last_frame_count = world->frame_counter; + create_jobs(); + } + + return CR_OK; +} + + +/* + * Interface hooks + */ +struct autogem_hook : public df::viewscreen_dwarfmodest { + typedef df::viewscreen_dwarfmodest interpose_base; + + bool in_menu() { + // Determines whether we're looking at the Workshop Orders screen. + return ui->main.mode == ui_sidebar_mode::OrdersWorkshop; + } + + bool handleInput(std::set *input) { + if (!in_menu()) { + return false; + } + + if (input->count(interface_key::CUSTOM_G)) { + // Toggle whether gems are auto-cut for this fort. + auto config = World::GetPersistentData(CONFIG_KEY, NULL); + if (config.isValid()) { + config.ival(0) = running; + } + + running = !running; + return true; + } + + return false; + } + + DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set *input)) { + if (!handleInput(input)) { + INTERPOSE_NEXT(feed)(input); + } + } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) { + INTERPOSE_NEXT(render)(); + if (in_menu()) { + auto dims = Gui::getDwarfmodeViewDims(); + int x = dims.menu_x1 + 1; + int y = dims.y1 + 12; + OutputHotkeyString(x, y, (running? "Auto Cut Gems": "No Auto Cut Gems"), "g", false, x, COLOR_WHITE, COLOR_LIGHTRED); + } + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(autogem_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(autogem_hook, render); + + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { + if (event == DFHack::SC_MAP_LOADED) { + if (enabled && World::isFortressMode()) { + // Determine whether auto gem cutting has been disabled for this fort. + auto config = World::GetPersistentData(CONFIG_KEY); + running = !(config.isValid() && config.ival(0)); + last_frame_count = world->frame_counter; + } + } else if (event == DFHack::SC_MAP_UNLOADED) { + running = false; + } + + return CR_OK; +} + +DFhackCExport command_result plugin_enable(color_ostream& out, bool enable) { + if (enable != enabled) { + if (!INTERPOSE_HOOK(autogem_hook, feed).apply(enable) || !INTERPOSE_HOOK(autogem_hook, render).apply(enable)) { + out.printerr("Could not %s autogem hooks!\n", enable? "insert": "remove"); + return CR_FAILURE; + } + + enabled = enable; + running = enabled && World::isFortressMode(); + } + + return CR_OK; +} + +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown(color_ostream &out) { + return plugin_enable(out, false); +} From e8ea5c93ca85b86977ce3b2f9b4b834f9f3c84da Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 9 Jan 2016 08:37:01 -0500 Subject: [PATCH 55/60] Build testHashMap.cpp in the build directory This allows switching between build directories without having to re-run CMake --- depends/protobuf/CMakeLists.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/depends/protobuf/CMakeLists.txt b/depends/protobuf/CMakeLists.txt index 39726c81a..f8a1bdab8 100644 --- a/depends/protobuf/CMakeLists.txt +++ b/depends/protobuf/CMakeLists.txt @@ -21,12 +21,12 @@ IF(CMAKE_COMPILER_IS_GNUCC) FOREACH(header tr1/unordered_map unordered_map) FOREACH(namespace std::tr1 std ) IF(HAVE_HASH_MAP EQUAL 0 AND NOT STL_HASH_OLD_GCC) - CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/testHashMap.cpp.in" "${CMAKE_CURRENT_SOURCE_DIR}/testHashMap.cpp") + CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/testHashMap.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/testHashMap.cpp") IF(CMAKE_CROSSCOMPILING) - TRY_COMPILE(HASH_MAP_COMPILE_RESULT ${PROJECT_BINARY_DIR}/CMakeTmp "${CMAKE_CURRENT_SOURCE_DIR}/testHashMap.cpp") + TRY_COMPILE(HASH_MAP_COMPILE_RESULT ${PROJECT_BINARY_DIR}/CMakeTmp "${CMAKE_CURRENT_BINARY_DIR}/testHashMap.cpp") SET(HASH_MAP_RUN_RESULT ${HASH_MAP_COMPILE_RESULT}) ELSE() - TRY_RUN(HASH_MAP_RUN_RESULT HASH_MAP_COMPILE_RESULT ${PROJECT_BINARY_DIR}/CMakeTmp "${CMAKE_CURRENT_SOURCE_DIR}/testHashMap.cpp") + TRY_RUN(HASH_MAP_RUN_RESULT HASH_MAP_COMPILE_RESULT ${PROJECT_BINARY_DIR}/CMakeTmp "${CMAKE_CURRENT_BINARY_DIR}/testHashMap.cpp") ENDIF() IF (HASH_MAP_COMPILE_RESULT AND HASH_MAP_RUN_RESULT EQUAL 1) SET(HASH_MAP_H <${header}>) @@ -45,8 +45,8 @@ IF(CMAKE_COMPILER_IS_GNUCC) FOREACH(header ext/hash_map hash_map) FOREACH(namespace __gnu_cxx "" std stdext) IF (HAVE_HASH_MAP EQUAL 0) - CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/testHashMap.cpp.in" "${CMAKE_CURRENT_SOURCE_DIR}/testHashMap.cpp") - TRY_COMPILE(HASH_MAP_COMPILE_RESULT ${PROJECT_BINARY_DIR}/CMakeTmp "${CMAKE_CURRENT_SOURCE_DIR}/testHashMap.cpp") + CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/testHashMap.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/testHashMap.cpp") + TRY_COMPILE(HASH_MAP_COMPILE_RESULT ${PROJECT_BINARY_DIR}/CMakeTmp "${CMAKE_CURRENT_BINARY_DIR}/testHashMap.cpp") IF (HASH_MAP_COMPILE_RESULT) SET(HASH_MAP_H <${header}>) STRING(REPLACE "map" "set" HASH_SET_H ${HASH_MAP_H}) From b62a8673bc5cf678051d56fb6a94f41a6cf3fdcf Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 9 Jan 2016 10:51:55 -0500 Subject: [PATCH 56/60] Bump version to 0.42.04-alpha2 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a8e29ebb5..891a130db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -103,7 +103,7 @@ endif() # set up versioning. set(DF_VERSION "0.42.04") -SET(DFHACK_RELEASE "alpha1") +SET(DFHACK_RELEASE "alpha2") SET(DFHACK_PRERELEASE TRUE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") From 08bebc94e1d59db53e7f3e23985a6cd691dc400b Mon Sep 17 00:00:00 2001 From: Ben Lubar Date: Sat, 9 Jan 2016 13:23:42 -0600 Subject: [PATCH 57/60] Fix dates being offset by 1 day in legends_plus.xml - Fixes #783 --- scripts/exportlegends.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/exportlegends.lua b/scripts/exportlegends.lua index b8c37efde..cc83dbf5f 100644 --- a/scripts/exportlegends.lua +++ b/scripts/exportlegends.lua @@ -85,7 +85,7 @@ end --create an extra legends xml with extra data, by Mason11987 for World Viewer function export_more_legends_xml() - local julian_day = math.floor(df.global.cur_year_tick / 1200) + 1 + local julian_day = math.floor(df.global.cur_year_tick / 1200) local month = math.floor(julian_day / 28) + 1 --days and months are 1-indexed local day = julian_day % 28 + 1 local year_str = string.format('%0'..math.max(5, string.len(''..df.global.cur_year))..'d', df.global.cur_year) From 7c9002c4ea9321d89067d261225ff98ab6200db7 Mon Sep 17 00:00:00 2001 From: Eric Wald Date: Sat, 9 Jan 2016 14:47:00 -0700 Subject: [PATCH 58/60] Avoid overwriting dynamic lines. Even vanilla DF has three additional workshop order lines that only show up with modded raws. --- plugins/autogems.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/autogems.cpp b/plugins/autogems.cpp index 4ae6b7efc..dcce37c3c 100644 --- a/plugins/autogems.cpp +++ b/plugins/autogems.cpp @@ -246,7 +246,15 @@ struct autogem_hook : public df::viewscreen_dwarfmodest { auto dims = Gui::getDwarfmodeViewDims(); int x = dims.menu_x1 + 1; int y = dims.y1 + 12; - OutputHotkeyString(x, y, (running? "Auto Cut Gems": "No Auto Cut Gems"), "g", false, x, COLOR_WHITE, COLOR_LIGHTRED); + Screen::Pen pen = Screen::readTile(x, y); + + while (pen.valid() && pen.ch != ' ') { + pen = Screen::readTile(x, ++y); + } + + if (pen.valid()) { + OutputHotkeyString(x, y, (running? "Auto Cut Gems": "No Auto Cut Gems"), "g", false, x, COLOR_WHITE, COLOR_LIGHTRED); + } } } }; From 5f03bb743f06857b5aec8d99bcbfaa3f20bdafe9 Mon Sep 17 00:00:00 2001 From: Eric Wald Date: Sat, 9 Jan 2016 16:47:06 -0700 Subject: [PATCH 59/60] Increasing autogems checks to once per day. A legendary gem cutter couldn't quite clear a full suite of jobs before the next check, but it was a close call sometimes. --- plugins/autogems.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/autogems.cpp b/plugins/autogems.cpp index dcce37c3c..099a0e570 100644 --- a/plugins/autogems.cpp +++ b/plugins/autogems.cpp @@ -20,7 +20,7 @@ #include "df/viewscreen_dwarfmodest.h" #define CONFIG_KEY "autogems/config" -#define DELTA_TICKS 1800 +#define DELTA_TICKS 1200 #define MAX_WORKSHOP_JOBS 10 using namespace DFHack; From 0bcc8dc4437761e36d3dae8a516dccca5a11eaf5 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 9 Jan 2016 19:28:12 -0500 Subject: [PATCH 60/60] exportlegends: fix day/month issues more reliably Fixes #783, #791 --- library/modules/World.cpp | 4 +++- scripts/exportlegends.lua | 5 ++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/library/modules/World.cpp b/library/modules/World.cpp index 66390da6e..3be400515 100644 --- a/library/modules/World.cpp +++ b/library/modules/World.cpp @@ -78,7 +78,9 @@ uint32_t World::ReadCurrentYear() uint32_t World::ReadCurrentTick() { - return DF_GLOBAL_VALUE(cur_year_tick, 0); + // prevent this from returning anything less than 0, + // to avoid day/month calculations with 0xffffffff + return std::max(0, DF_GLOBAL_VALUE(cur_year_tick, 0)); } bool World::ReadGameMode(t_gamemodes& rd) diff --git a/scripts/exportlegends.lua b/scripts/exportlegends.lua index cc83dbf5f..a0a04d9a2 100644 --- a/scripts/exportlegends.lua +++ b/scripts/exportlegends.lua @@ -85,9 +85,8 @@ end --create an extra legends xml with extra data, by Mason11987 for World Viewer function export_more_legends_xml() - local julian_day = math.floor(df.global.cur_year_tick / 1200) - local month = math.floor(julian_day / 28) + 1 --days and months are 1-indexed - local day = julian_day % 28 + 1 + local month = dfhack.world.ReadCurrentMonth() + 1 --days and months are 1-indexed + local day = dfhack.world.ReadCurrentDay() local year_str = string.format('%0'..math.max(5, string.len(''..df.global.cur_year))..'d', df.global.cur_year) local date_str = year_str..string.format('-%02d-%02d', month, day)